Build your content-driven website now with Contentlayer and Nextjs


Why do you need one?

Not only does having a blog website help you to establish your authority and expertise, but it also allows you to connect with like-minded individuals and build a community around your content. Additionally, a blog website can be a valuable tool for driving traffic to your other online platforms, such as social media profiles or business websites.

Let's start

We will be using mdx, contentlayer and nextjs 13 for this tutorial.

  • Mdx - Allows you to write markdown content with custom react components

  • Contentlayer - Contentlayer is the glue between your content and your code.

  • Nextjs 13 - Out of the box Seo and performance

Project setup

First, let's create a new Next.js application, you can follow the official guide here. I will be using npm for this, but you can use yarn or pnpm as well.

  • Create a new Next.js project.
npx create-next-app contentlayer-example
cd contentlayer-example

Install Dependencies

npm install contentlayer next-contentlayer

To hook Contentlayer into the next dev and next build processes, you'll want to wrap the Next.js configuration using the withContentlayer method. In the nextjs.config.js add this code.

const { withContentlayer } = require("next-contentlayer")

/** @type {import('next').NextConfig} */
const nextConfig = {
  // your nextjs config
  ...
}

module.exports = withContentlayer(nextConfig)

Now add these lines to your jsconfig.json or tsconfig.json if using typescript

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "contentlayer/generated": ["./.contentlayer/generated"]
    }
  },
  "include": ["next-env.d.ts", "**/*.tsx", "**/*.ts", ".contentlayer/generated"]
}

Contentlayer generates files in the .contentlayer/generated directory. This tells JavaScript (or TypeScript) to create an alias of contentlayer/generated to the generated files directory.

Now Define Schemas

In the root create a file contentlayer.config.js and define a schema for your blog post.

export const Post = defineDocumentType(() => ({
  name: "Post",
  filePathPattern: `blog/**/*.mdx`,
  contentType: "mdx",
  fields: {
    title: {
      type: "string",
      required: true,
    },
    description: {
      type: "string",
    }
  },
  computedFields: {
    slug: {
    type: "string",
    resolve: (doc) => `/${doc._raw.flattenedPath}`,
  },
  slugAsParams: {
    type: "string",
    resolve: (doc) => doc._raw.flattenedPath.split("/").slice(1).join("/"),
  },
  },
}))

export default makeSource({
  contentDirPath: "blogs",
  documentTypes: [Page, Doc, Guide, Post, Author],
})

This configuration specifies a single document type called Post. These documents are expected to be .mdx files that live within a blogs directory in your project. And data objects generated from these files will have the following properties:

  • title, description: String pulled from the file's frontmatter.

  • body: An object that contains the raw content from the markdown file and the converted html string. (This is built into Contentlayer by default and does not have to be defined.)

  • slug: string defining the whole URL of the content

    • eg. /blogs/make-your-own-site
  • slugAsParams: string defining slug as params

    • eg. /make-your-own-site

you can add more fields to your blogs by defining them in the fields object

Add blog content

Create a few markdown files in a blogs directory and add some content to those files. Here's an example of what a post file at posts/build-your-site.mdx might look like:

---
title: Lorem Ipsum
date: 2021-12-24
---

Your Content

you can add react components here as well  (later in this blog)

Render it to UI

Now everything is set up and the content is generated in ./contentlayer/generated and you just need to show it.

Copy the code from mdx.ts file here and paste it into a mdx-components.js . the code contains basic mdx elements you can extend it with your custom react ui components as well.

// [...slug].js

import { Mdx } from "@/components/markdown/mdx-components";
import { allBlogs } from "@/.contentlayer/generated";

async function getDocFromParams(params) {
  const slug = params.slug?.join("/") || "";
  const blog = allBlogs.find((blog) => blog.slugAsParams === slug);
  if (!blog) {
    null;
  }
  return blog;
}

const page = async() => {
    const blog = await getDocFromParams(params);
    return (
          <div className="mx-auto w-full min-w-0">
               <title>{post.title}</title>
               <p>{blog.description}</p>
            <Mdx code={blog.body.code} />
          </div>
     )
}

Here are some more customization and styling

you can copy-paste files from here and add it your project to make your mdx look as you want

That's all

Now you have your own site with custom mdx customizations.

Did you find this article valuable?

Support Manish Bisht by becoming a sponsor. Any amount is appreciated!