# Build up the blog https://www.sanity.io/learn/course/content-driven-web-application-foundations/build-up-the-blog.md With all the basics in place, let's blow out our blog front end into something more visually impressive. ![A nicely designed post index page](https://cdn.sanity.io/images/3do82whm/next/456fd8b7757f0c0a8a279a154b0fb75f5b5ba02c-2144x1388.png) For the remaining courses in this track, a much richer front end that requests and renders more content from your three schema types will be helpful. In this lesson, you'll build the blog into something more interesting. ## Install new dependency To format date strings returned from Sanity documents, install [Day.js](https://www.npmjs.com/package/dayjs). 1. **Run** the following to install Day.js ```sh pnpm add dayjs ``` ## Update queries and types 1. **Update** your queries to request more content, including resolving `category` and `author` references. ```typescript:src/sanity/lib/queries.ts import { defineQuery } from 'next-sanity' export const POSTS_QUERY = defineQuery(`*[_type == "post" && defined(slug.current)]|order(publishedAt desc)[0...12]{ _id, title, slug, body, mainImage, publishedAt, "categories": coalesce( categories[]->{ _id, slug, title }, [] ), author->{ name, image } }`) export const POSTS_SLUGS_QUERY = defineQuery(`*[_type == "post" && defined(slug.current)]{ "slug": slug.current }`) export const POST_QUERY = defineQuery(`*[_type == "post" && slug.current == $slug][0]{ _id, title, body, mainImage, publishedAt, "categories": coalesce( categories[]->{ _id, slug, title }, [] ), author->{ name, image } }`) ``` 1. **Run** Typegen to update your query Types ```sh pnpm run typegen ``` 1. This command was setup during [Generate TypeScript Types](https://www.sanity.io/learn/course/content-driven-web-application-foundations/generate-typescript-types) ## Create new components As some content will be rendered on both the post index and the individual post routes, abstracting these elements into components helps keep code somewhat DRY (don't repeat yourself). You may like to adapt the Tailwind CSS class names to your liking. 1. Create a new directory — `/src/components` — in your Next.js application for storing components. These aren't stored in `/app` since that directory is primarily for generating routes. 1. **Create** an `Author` component ```tsx:src/components/author.tsx import { POST_QUERYResult } from '@/sanity/types' import { urlFor } from '@/sanity/lib/image' import Image from 'next/image' type AuthorProps = { author: NonNullable['author'] } export function Author({ author }: AuthorProps) { return author?.image || author?.name ? (
{author?.image ? ( {author.name ) : null} {author?.name ? (

{author.name}

) : null}
) : null } ``` 1. **Create** a `Categories` component ```tsx:src/components/categories.tsx import { POST_QUERYResult } from '@/sanity/types' type CategoriesProps = { categories: NonNullable['categories'] } export function Categories({ categories }: CategoriesProps) { return categories.map((category) => ( {category.title} )) } ``` 1. **Create** a `PublishedAt` component ```tsx:src/components/published-at.tsx import { POST_QUERYResult } from '@/sanity/types' import dayjs from 'dayjs' type PublishedAtProps = { publishedAt: NonNullable['publishedAt'] } export function PublishedAt({ publishedAt }: PublishedAtProps) { return publishedAt ? (

{dayjs(publishedAt).format('D MMMM YYYY')}

) : null } ``` 1. **Create** a `Title` component for rendering a page title in a `

` ```tsx:src/components/title.tsx import { PropsWithChildren } from 'react' export function Title(props: PropsWithChildren) { return (

{props.children}

) } ``` 1. **Create** a `Post` component for rendering the above components on a single post page ```tsx:src/components/post.tsx import { PortableText } from 'next-sanity' import Image from 'next/image' import { Author } from '@/components/author' import { Categories } from '@/components/categories' import { components } from '@/sanity/portableTextComponents' import { POST_QUERYResult } from '@/sanity/types' import { PublishedAt } from '@/components/published-at' import { Title } from '@/components/title' import { urlFor } from '@/sanity/lib/image' export function Post(props: NonNullable) { const { title, author, mainImage, body, publishedAt, categories } = props; return (
{title}
{mainImage ? (
) : null} {body ? (
) : null}
); } ``` 1. **Create** a `PostCard` component for rendering the above components on the post index page ```tsx:src/components/post-card.tsx import Link from 'next/link' import Image from 'next/image' import { Author } from '@/components/author' import { Categories } from '@/components/categories' import { POSTS_QUERYResult } from '@/sanity/types' import { PublishedAt } from '@/components/published-at' import { urlFor } from '@/sanity/lib/image' export function PostCard(props: POSTS_QUERYResult[0]) { const { title, author, mainImage, publishedAt, categories } = props return (

{title}

{mainImage ? ( {mainImage.alt ) : null}
) } ``` 1. **Create** a `Header` component for the top nav of the site ```tsx:src/components/header.tsx import Link from 'next/link' export function Header() { return (
Layer Caker
  • Posts
  • Sanity Studio
) } ``` ## Update your routes Now that you have many small components, it's time to import them into your routes to complete the design. 1. **Update** the root layout to display the site-wide navigation ```tsx:src/app/(frontend)/layout.tsx import { Header } from '@/components/header' import { SanityLive } from '@/sanity/lib/live' export default function FrontendLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return (
{children}
) } ``` 1. **Update** the root page to use the `Title` component ```tsx:src/app/(frontend)/page.tsx import Link from 'next/link' import { Title } from '@/components/title' export default async function Page() { return (
Layer Caker Home Page
Posts index →
) } ``` 1. **Update** the post index page to use the `PostCard` component ```tsx:src/app/(frontend)/posts/page.tsx import Link from 'next/link' import { sanityFetch } from '@/sanity/lib/live' import { POSTS_QUERY } from '@/sanity/lib/queries' export default async function Page() { const { data: posts } = await sanityFetch({ query: POSTS_QUERY }) return (

Post index

    {posts.map((post) => (
  • {post?.title}
  • ))}

← Return home
) } ``` 1. **Update** the individual post route to use the `Post` component ```tsx:src/app/(frontend)/posts/[slug]/page.tsx import { notFound } from 'next/navigation' import { sanityFetch } from '@/sanity/lib/live' import { POST_QUERY } from '@/sanity/lib/queries' import { Post } from '@/components/post' export default async function Page({ params, }: { params: Promise<{ slug: string }> }) { const { data: post } = await sanityFetch({ query: POST_QUERY, params: await params, }) if (!post) { notFound() } return (
) } ``` ## All done! Click around the site now. You should have a richer site-wide header, post index, and individual post pages. ![A blog post web page with a nice design](https://cdn.sanity.io/images/3do82whm/next/bf2cc448c4f1ce6b4b05a74c29218c92da511323-2144x1388.png) You're also in a much better position for the remaining lessons in this track. Let's test what you've learned in the final lesson.