If you wanted to host a personal website with a blog but didn't want to utilize a backend to fetch the blog posts from a database,
you probably explored the markdown option before.
In this guide, I'll share my experience creating a content management system using Next.js 14 and MDX.
Github Repo
If you want to visit the final product and explore the code yourself or just clone the project and make your own blog, go to the github repo here.
Overview
In this development guide we will create a Next.js 14 app router project with static exports
We will utilize the MDX library to render Markdown blog posts.
We will create our first Markdown blog post.
What is Static Site Generation (SSG)
To summarize; Next.js can generate an individual html file for each route in the application.
If a route is a dynamic route like /blog/[...blogId] then you need to generate each possible blogId for that path.
This approach enables us to run the next build command and get the generated full website in the /out folder.
You can just copy the contents of that folder and host it on any webserver as a static site.
Markdown is a text markup language. It's widely adapted. For example, github repo's will detect the readme.md file in the current directory and display it below.
MDX is a js library that allows us to import a markdown file as a react component and use it anywhere.
We will write our blog posts using markdown syntax and save them as an mdx file.
We can then import the post and use it anywhere in our app thanks to these tools.
Getting Started
Lets create a new next.js app using the terminal and choose the default options including tailwind and app router.
npx create-next-app@latest
We can now remove default content from layout.tsx, page.tsx and globals.css. and create a basic root layout
Remove styles
Page.tsx is the home / directory of the page.
Creating the file structure
Lets create our file structure for our rotes and the folder where our markdown blog posts will live.
The blog/[blogId]/page.tsx file will be called when we are viewing a blog post and the blog/page.tsx will be the page to display all blog post links. /blogs folder will keep our MDX files.
To enable viewing MDX files in our app, lets install MDX.
We should also install our other helper which is tailwindcss/typography as a dev dependency.
This'll be useful later for auto styling the blog post.
Lets now create a sample MDX file at /blogs/<post_name>.mdx.
Before we start using the mdx content, we must configure Next.js
MDX Component file
Lets create the necessary mdx-components.tsx file at the root directory next to /app etc.
This will be auto imported by MDX and the components in this will be used to wrap each markdown element
when rendering the mdx content in jsx. If you want to customize a specific element like an h1, this is the place to do it. For further information, visit next.js docs.
Next Config for MDX and Static Exports
We need to configure next.config.js but if we want markdown plugins like remark-gfm which allows Github flavoured markdown to be rendered, we need to rename the next.config.js file to next.config.mjs
Lets first install remark-gfm npm i remark-gfm so we can use enhanced markdown features of Github flavoured markdown.
Lets also configure next.js to use static exports by adding output: "export" to the config object.
Generating static pages
Before we can continue, if we now visit /blog/first_blog our app will throw an error because we are now using static site generation. We must provide the route with all possible blogId's in a function called
generateStaticParams.
Lets go to blog/[blogId]/page.tsx and manually create one. we will later come back to this.
Now we can visit blog/first_blog. Please note that visiting any other blogId will result in an error in dev server but it will just show a 404 error page in production (after build).
Rendering our first blog
We are now ready to import our markdown file and render it! Reminder: make sure you've created the mdx-components.tsx at the root directory.
We need to use next/dynamic to import our markdown as a component in blog/[blogId]/page.tsx
When we now visit blog/first_blog we should see our blog post rendered in the layout.
Adding Meta Data to blog posts
Since we are using mdx, we can export anything from a mdx file like a normal js file.
Lets export a const called metadata and define some metadata for each blog post there.
Blog Helper Functions
Now that we have exports from a mdx file, we need a way to import that file and access its exports.
We need to do this on both blogs page and individual blog post pages so we can create a few helper functions to streamline this process.
lets create a /lib folder and our helper functions file /lib/blog_functions.ts
we may now use this hook to easily load metadata for any blogId. Lets use this data to display the correct title and description for the blog page.
You will now notice that your page title is loaded from first_blog.mdx metadata.
Styling the post: Tailwind Typography
We had installed the tailwind typography plugin before. Now we can use it to easily style our blog post.
In the <article> tag wrapping the blog post, lets add the following classes:
now for this to work, me must configure tailwind config. Go to your project root and open tailwind.config.ts and add the typography plugin to plugins array.
after reloading the dev server, when we visit blog/first_blog we should now see a styled blog page.
Generating the blog list
Now that we are able to view each blog post, lets figure out a way to generate a list of blog posts to show at /blog page.
lets go to /lib/blog_functions.ts and create a function that uses the node filesystem fs to search entire /blog directory and return an array of blogId's available for us.
Now we can import the function in the blogs page and list all blog posts
generateStaticParams for all blog posts
Now only thing left is generating static params for all routes. We need to read all filenames /blog directory and create a static route for each of them in app/blog/[blogId]/page.tsx
Lets first create another helper function to get all filenames from the blog directory
Now we can go to our blog page and generate static params
Conclusion
We now have a working blog. All we need to do to create more posts is to create a new .mdx file at /blog directory. We need to export export const metadata in each post or the app will throw error.
Final blog post example:
Here is a screenshot of the /blog page
Further reading
You can now run next build command and copy the /out folder to any web server. This process needs to be repeated after each new blog post.
In our next adventure, we we will learn how to deploy our blog to Github Pages and automate this process.