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.
// ...
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}`}
>
← {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} →
</Link>
)}
</footer>
</SectionContainer>
);
}