Building a blog with App Router, React Server Components and Tailwind
Published on 2024-03-28
As 2024 started I finally got around to working on the blog portion of this site. While I was having a lot of fun building out the other pages (see Building a new website with Next.js 14 and App Router) I knew that I needed to get to work on the blog portion so that I could write about all the work I had been doing! Thankfully I wasn't starting this endeavour from scratch as @max_leiter, who works at Vercel had written an extremely thorough tutorial on how to do this. I encourage you to read Building a blog with Next.js 14 and React Server Components as he goes into far more detail on all the steps needed to start from scratch. I didn't want to recreate Max's blog post so what I'm presenting here are some of the changes I made.
Tailwind#
I've been building my site using shadcn/ui, which leverages Tailwind so I figured I
would stick with this approach when building out the blog portion as well. I discovered that Tailwind has a
typography plugin which adds the prose
class.
The @tailwindcss/typography plugin adds a set of prose classes that can be used to quickly add sensible typographic styles to content blocks that come from sources like markdown or a CMS database.
This is exactly what I wanted and using Route Groups
allowed me to wrap all the content in the blog and any other page I want to render with Markdown in the prose
class.
You can look at my layout.tsx but the contents are shown below.
There is functionality for dark mode as well with dark:prose-invert
! With this layout in place I was extremely happy
to not worry about anymore styling for any of my content pages.
No next-mdx-remote#
In Max's Fetching and rendering markdown
section of his blog, he mentions that he wants to use next-mdx-remote
for niche specific reasons. I don't have any reasoning to do that, so I wanted to change the approach and ensure that
all my Markdown files are rendered the same way. What I mean by that is a regular .mdx
file can be rendered as a page
component and the Getting Started
section of Markdown and MDX documentation for Next.js shows how this is done. Instead of passing the plugins and the
contents of the mdx-components.tsx
file to next-mdx-remote
, I want to leverage the createMdx
functionality described
in the Getting Started tutorial above. I'll cover my changes to createMdx
and mdx-components.tsx
in the next
section.
My fetchPosts.ts differs a bit from
how Max set his up. Lets go through the changes that allowed me to stop using next-mdx-remote
.
First up is parseMdxFiles
.
In parseMdxFiles
we're reading the checked in blog posts at ./posts
and constructing an array of Post
objects. Those objects contain the frontmatter, which is extracted using
gray-matter and the actual post content. We're also using the new
cache feature from React create parsedMdxFiles
to ensure that we only ever
have to parse these files once, which is what we want because we're going to reference them a few more times.
The next function is postComponents
.
Here we're using our cached parsedMdxFiles
and building up a hash of React components, which are the rendered
content of each Markdown file. Since we're in the world of React Server Components, we don't need to do any sort of
Lazy Loading as the docs state
Lazy loading applies to Client Components.
Instead, we can use await import
as a way to dynamically load each component. It took me a long time to come to this
conclusion and I nearly gave up; I was going to manually import each blog post after trying to use React.lazy
and next/dynamic
to achieve this, but I was happy to finally come to the realization I can just await import
.
We can then use this function in
slug/page.tsx
as so.
With that, I was able to get the blog posts rendering without the use of next-mdx-remote
.
My createMDX and mdx-components.tsx Setup#
Now that the blog posts are rendering, I wanted to add some rehype plugins to give a little extra functionality to
each post. I added rehype-slug and
rehype-autolink-headings so that I could create linkable
header elements. In the end my createMdx
in
next.config.mjs function looked like this
The next step was to create a nice clickable header that would be rendered. I decided <h2>
would be where I would
start with this and if I wanted to add more clickable elements I could expand later. I created an
H2WithAnchor component
that looks like this.
Then I added it to my mdx-components.tsx.
Now there is a nice #
that appears when hovering over the <h2>
elements and will change the route to be anchored
on the heading. You can click on
building-a-blog-with-app-router-rsc-tailwind#my-createmdx-and-mdx-componentstsx-setup
to land right back here!
I've added a handful of other components into the mdx-components.tsx
file and am really liking the ability to mix
React into my preferred way of writing.
Nitpicking Bright's Code Highlighting#
Part of Max's tutorial was choosing bright as the code highlighting solution. It
was extremely easy to set up and with a variety of different themes, I was able to find one that I really liked and
matched the color scheme on my site pretty closely. I chose solarized
, which I found from the
Code Hike Themes page.
One of my favorite things to do when writing Markdown files is to use the backticks
to highlight inline code. However,
I noticed that the current implementation was not actually applying any styling and I was ending up with words that
looked like `this`. I looked at the HTML that was being rendered and it was a <code>
tag so I figured I
could update my mdx-components.tsx
file to have something like
and see the changes. Instead I was presented with this nice error message
I did some investigation and eventually opened a ticket
Attempting to adjust 'code' in mdx-components breaks. I followed up
two days later with a re-creation and potential
solution to the issue. Unfortunately minutes after posting that comment my house was struck with a weather related
catastrophe and I completely forgot about attempting to create a PR to remedy the issue. Much to my surprise,
a few months later @joshwcomeau, whom I follow on Twitter, opened a PR referencing
the issue I created. He was experiencing the same thing and wanted to be able to utilize adding styling for his React
course The Joy of React (you should check it out, he'll teach you to build a blog as well!)
The maintainer of the project, @pomber, came in, tidied up the code a bit more, and we
had ourselves a new 0.8.5 release of bright
!
Needless to say, my original code changes I detailed in the issue weren't anywhere close to what was merged, I was
overjoyed to be a part of open source collaboration. Having multiple people are able to come together to improve
software that we all enjoy using is an awesome experience. Now I can highlight
as much
as I want to
!
Conclusion#
I learned a lot while putting all this together and I'm really happy with the results. For now, I've got everything in a place where I won't need to spend any more time working on setting up the blog and I can focus on writing more content!