Static Site Generation (SSG) is a powerful feature of Next.js that allows you to pre-generate HTML pages at build time. This approach enhances performance, improves SEO, and reduces server load by serving pre-rendered content instead of dynamically generating it on every request. In this article, we’ll explore how static rendering works in MDXBlog.
Introduction to Static Site Generation
Static site generation is the process where HTML pages are generated at build time and served as static files. In MDXBlog, this is achieved through a combination of Next.js’s static generation capabilities and the integration of MDX files, which allow for rich, interactive content.
How MDX is Integrated into MDXBlog
MDXBlog uses the @next/mdx package to seamlessly integrate MDX files into the Next.js build process. MDX allows you to write JSX within Markdown, enabling the use of React components in your content. The following configuration in next.config.mjs shows how MDX is set up:
This configuration allows MDX files to be treated as standard Next.js pages or components. It also enables the use of plugins like remarkGfm and rehypeHighlight to extend the capabilities of your Markdown content.
Understanding the Build Process
During the build process, Next.js compiles your MDX files into React components. These components are then rendered into static HTML files, which are served to users when they request a page.
Dynamic Imports and Static Rendering
In MDXBlog, dynamic imports are used to load MDX files based on a slug. Next.js resolves these imports at build time for pre-defined pages, allowing the MDX content to be statically rendered into HTML. This approach is implemented directly within the page component for each dynamic route:
typescript
// /app/blog/[slug]/page.tsximportReactfrom"react";
importtype { Metadata } from"next";
import { notFound } from"next/navigation";
import path from"node:path";
import fs from"node:fs";
importRelatedPostsListfrom"@/components/related-posts";
importLikeButtonfrom"@/components/like/like-button";
importEditPostButtonfrom"./edit-post-button";
importOpenInVSCodefrom"./open-in-vs-code-button";
import { isDevMode } from"@/lib/utils/is-dev-mode";
typeProps = {
params: { slug: string };
};
// Dynamically import the MDX file based on the slugasyncfunctionloadMdxFile(slug: string) {
try {
const mdxPath = path.join(process.cwd(), "content", "posts", `${slug}.mdx`);
if (!fs.existsSync(mdxPath)) {
returnnull;
}
const mdxModule = awaitimport(`@/content/posts/${slug}.mdx`);
return mdxModule;
} catch (error) {
console.error("Failed to load MDX file:", error);
returnnull;
}
}
// Generate metadata using the imported metadata from the MDX fileexportasyncfunctiongenerateMetadata({ params }: Props): Promise<Metadata> {
const mdxModule = awaitloadMdxFile(params.slug);
if (!mdxModule) {
return {
title: "Post Not Found",
description: "",
};
}
const { metadata } = mdxModule;
return {
title: metadata.title,
description: metadata.description,
};
}
exportdefaultasyncfunctionBlog({ params }: Props) {
const { slug } = params;
const mdxModule = awaitloadMdxFile(slug);
if (!mdxModule) {
notFound(); // Return a 404 page if the MDX file is not found
}
const { default: Content, metadata } = mdxModule;
// Extract the dates and compare themconst publishDate = newDate(metadata?.publishDate);
const modifiedDate = newDate(metadata?.modifiedDate);
// Choose the date to displayconst displayDate =
modifiedDate > publishDate ? metadata?.modifiedDate : metadata?.publishDate;
return (
<divclassName="max-w-3xl z-10 w-full items-center justify-between"><divclassName="w-full flex justify-center items-center flex-col gap-6"><articleclassName="prose prose-lg md:prose-lg lg:prose-lg mx-auto min-w-full"><divclassName="pb-6"><pclassName="font-semibold text-lg"><spanclassName="text-primary pr-1"title="Date last modified">
{displayDate.split("T")[0]}
</span>{" "}
{metadata?.categories?.map((category: string, index: number) => (
<spankey={index + category} title="Post category">
{category}
{index < metadata?.categories.length - 1 && ", "}
</span>
))}
</p></div><divclassName="pb-6"><h1className="text-4xl sm:text-6xl font-black capitalize leading-12">
{metadata?.title}
</h1><pclassName="pt-6 text-xl sm:text-lg">By {metadata?.author}</p></div>
{/* Render the dynamically loaded MDX content */}
{isDevMode() && (
<divclassName="flex gap-2 mb-4"><EditPostButtonslug={slug} /><OpenInVSCodepath={slug} /></div>
)}
<Content /></article></div><div><div><RelatedPostsListrelatedSlugs={metadata?.relatedPosts} /></div></div><LikeButtonpostId={metadata?.id} /></div>
);
}
// Generate static paths for all slugs based on MDX files in the posts directoryexportasyncfunctiongenerateStaticParams() {
const files = fs.readdirSync(path.join("content", "posts"));
const params = files
.filter((filename) => filename.endsWith(".mdx")) // Only process MDX files
.map((filename) => ({
slug: filename.replace(".mdx", ""),
}));
return params;
}
Handling Dynamic Routes with generateStaticParams
For dynamic routes like /blog/[slug], MDXBlog uses the generateStaticParams function to pre-generate paths for all MDX files in the content/posts directory. This ensures that these pages are also pre-rendered during the build process, maintaining the benefits of SSG even for dynamic content.
Your mdx-components.tsx file customizes how different HTML elements and components are rendered within MDX content. This customization ensures that your content is styled consistently and utilizes the power of React components:
These custom components are applied during the MDX compilation process, ensuring that when the content is rendered, it adheres to your design and functionality specifications.
The Role of generateMetadata
In a page component, the generateMetadata function plays a critical role in extracting and setting metadata from the MDX file. Here’s how it works:
This function ensures that the correct metadata, such as the title and description, is included in the pre-rendered HTML, which is essential for SEO and user experience.
Why Static Rendering Matters
Static rendering offers several benefits:
Performance: Pre-rendered pages load faster because they don’t need to be generated on the fly.
SEO: Search engines can easily index static pages, improving your site’s visibility.
Scalability: Serving static content reduces server load, making it easier to handle large volumes of traffic.
In MDXBlog, static site generation combines with the dynamic capabilities of React and MDX, providing a powerful platform that balances flexibility with performance.
Conclusion
In MDXBlog, static site generation is achieved through a combination of MDX processing, dynamic imports resolved at build time, and custom component rendering. This approach ensures that your content is served as fast, SEO-friendly static pages while retaining the flexibility and interactivity of modern React applications.
By understanding how static rendering works in MDXBlog, we can better optimize our content delivery and ensure a seamless user experience across all pages. Whether you're creating new content or extending the functionality of a blog, this static-first approach will serve as a solid foundation for future growth.