Creating a blog layout

Updated on

Layouts are powerful way to give a different look & feel to your blog articles.
However, they also enable you to transform your blog into a content hub, media gallery or even a documentation site.

Available layouts

There are a number of layouts available out of the box. You can use them by setting the layout property in your markdown file's frontmatter.

title: 'Creating a custom React component to use in the (mdx) blog'
date: '2023-11-11'
tags: ['blog', 'guide', 'mdx', 'markdown', 'documentation']
+ layout: PostSimple

Learn more about the built-in layouts in the Layouts section.

Adding a custom layout

Start by creating a new React component. A good starting point is to copy the existing PostSimple component.

Creating a new React component

For the sake of simplicity, we'll use an even more basic layout in this guide:

import { ReactNode } from 'react';
import { CoreContent } from '@shipixen/pliny/utils/contentlayer';
import type { Blog } from 'shipixen-contentlayer/generated';
import PageTitle from '@/components/shared/PageTitle';
import SectionContainer from '@/components/shared/SectionContainer';
import ScrollTop from '@/components/shared/ScrollTop';

interface LayoutProps {
  content: CoreContent<Blog>;
  children: ReactNode;
}

export default function PostLayout({ content, children }: LayoutProps) {
  const { date, title } = content;

  return (
    <SectionContainer type="wide">
      <ScrollTop />
      <article>
        <header>
          <PageTitle>{title}</PageTitle>
        </header>

        <div className="prose max-w-none pb-8 pt-10 dark:prose-invert">
          {children}
        </div>
      </article>
    </SectionContainer>
  );
}

We expect the layout to receive a content prop, which is the metadata of the blog post, and a children prop, which is the content of the blog post.

The content prop is of type CoreContent<Blog>, which is a generic type that represents the metadata of a blog post. Here's what it contains:

type Blog = {
  type: 'Blog';
  title: string;
  date: IsoDateTimeString;
  tags: string[];
  lastmod?: IsoDateTimeString | undefined;
  draft?: boolean | undefined;
  summary?: string | undefined;
  images?: any | undefined;
  authors?: string[] | undefined;
  layout?: string | undefined;
  bibliography?: string | undefined;
  canonicalUrl?: string | undefined;
  body: MDX;
  readingTime: json;
  slug: string;
  path: string;
  filePath: string;
  toc: string;
  structuredData: json;
};

Registering the layout

To be able to use this layout, we need to import it in app/[...slug]/route.tsx and register it in the layouts object.

app/[...slug]/route.tsx
// ...

import PostSimple from '@/layouts/PostSimple';
import PostLayout from '@/layouts/PostLayout';
import PostBanner from '@/layouts/PostBanner';
import PostHub from '@/layouts/PostHub';
+ import NewLayout from '@/layouts/NewLayout';

import { Metadata } from 'next';
import { siteConfig } from '@/data/config/site.settings';

const defaultLayout = 'PostLayout';
const layouts = {
  PostSimple,
  PostLayout,
  PostBanner,
  PostHub,
+ NewLayout,
};

Using the layout

Now that the layout is registered, we can use it in our markdown file's frontmatter:

title: 'Creating a custom React component to use in the (mdx) blog'
date: '2023-11-11'
tags: ['blog', 'guide', 'mdx', 'markdown', 'documentation']
- layout: PostSimple
+ layout: NewLayout

Advanced usage

Using other content fields

We've only used the title, but you'll usually want to render other fields in the layout such as the date, tags, etc.

For example, let's display the date:

import { formatDate } from '@shipixen/pliny/utils/formatDate';

...

  const { date, title } = content;

  <div>
    <dt className="sr-only">Published on</dt>
    <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
      <time dateTime={date}>
        {formatDate(date, siteConfig.locale)}
      </time>
    </dd>
  </div>

...

Prev and Next props

The layout also receives optional prev and next props, which are the metadata of the previous and next blog posts.

Those can be used to render a "Previous" and "Next" links at the bottom of the blog post.

export default function PostLayout({
  content,
  next,
  prev,
  children,
  className,
}: LayoutProps) {
  const { title } = content;

  return (
    <SectionContainer type="wide" className={cn(className)}>
      ...
      <footer>
        {prev && prev.path && (
          <Link
            href={`/${prev.path}`}
            className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
            aria-label={`Previous post: ${prev.title}`}
          >
            &larr; {prev.title}
          </Link>
        )}

        {next && next.path && (
          <Link
            href={`/${next.path}`}
            className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
            aria-label={`Next post: ${next.title}`}
          >
            {next.title} &rarr;
          </Link>
        )}
      </footer>
    </SectionContainer>
  );
}