In the ever-evolving landscape of web development, creating clear, concise, and easily navigable documentation is paramount. Whether you’re building a software library, an API, or simply want to document your personal projects, a well-structured documentation site can significantly enhance user experience and adoption. Next.js, with its powerful features and flexibility, provides an excellent framework for building such a site. This tutorial will guide you through the process of creating a Markdown-powered documentation site using Next.js, empowering you to create beautiful and functional documentation with ease.
Why Build a Documentation Site with Next.js?
Next.js offers several advantages that make it an ideal choice for documentation sites:
- Performance: Next.js leverages features like static site generation (SSG) and server-side rendering (SSR) to deliver fast-loading pages, crucial for a positive user experience.
- SEO Optimization: Next.js’s built-in SEO capabilities, including support for meta tags and sitemaps, help your documentation rank well in search engines.
- Developer Experience: With features like hot module replacement (HMR) and a robust ecosystem of plugins and libraries, Next.js streamlines the development process.
- Markdown Support: Next.js seamlessly integrates with Markdown, allowing you to write documentation in a simple, readable format.
- Customization: You have complete control over the design and functionality of your documentation site, allowing you to tailor it to your specific needs.
Setting Up Your Next.js Project
Let’s start by setting up a new Next.js project. Open your terminal and run the following command:
npx create-next-app documentation-site
cd documentation-site
This command creates a new Next.js project named “documentation-site” and navigates you into the project directory. Next, install some dependencies we’ll need for this project. We’ll use a Markdown parser and a library to style our Markdown content:
npm install gray-matter remark remark-html
npm install @mdx-js/react @mdx-js/loader
npm install next-mdx-remote
npm install rehype-slug rehype-autolink-headings
- gray-matter: This library helps parse the frontmatter (metadata) from your Markdown files.
- remark: A Markdown processor, used to transform Markdown content.
- remark-html: A plugin for remark that converts Markdown to HTML.
- @mdx-js/react and @mdx-js/loader: These are used to parse MDX files.
- next-mdx-remote: Used to render remote MDX content.
- rehype-slug and rehype-autolink-headings: These are used to generate slugs for headings and automatically link to them.
Creating the Markdown Files
Next, let’s create some Markdown files to serve as our documentation content. Create a directory named “docs” in the root of your project. Inside the “docs” directory, create a few Markdown files. For example, create files like “getting-started.md”, “api-reference.md”, and “examples.md”. Each file will contain your documentation content, formatted using Markdown syntax. Here’s an example of “getting-started.md”:
---
title: Getting Started
description: Learn how to set up and use our product.
---
# Getting Started
## Installation
Install our package using npm:
```bash
npm install my-package
```
## Configuration
Configure the package in your project:
```javascript
import MyPackage from 'my-package';
const config = {
apiKey: 'YOUR_API_KEY',
};
const instance = new MyPackage(config);
```
## Usage
Use the package to perform actions:
```javascript
const result = await instance.doSomething();
console.log(result);
```
Notice the frontmatter at the beginning of the file. This is where you can store metadata such as the title and description, which can be useful for generating a table of contents or SEO purposes.
Fetching and Processing Markdown
Now, let’s write the code to fetch and process these Markdown files in our Next.js application. Create a new directory called “lib” in your project root. Inside “lib”, create a file called “docs.js”. This file will contain functions to read Markdown files and parse their content.
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { remark } from 'remark';
import html from 'remark-html';
import prism from 'remark-prism';
import slug from 'rehype-slug';
import autolinkHeadings from 'rehype-autolink-headings';
const docsDirectory = path.join(process.cwd(), 'docs');
export function getSortedDocsData() {
// Get file names under /docs
const fileNames = fs.readdirSync(docsDirectory);
const allDocsData = fileNames.map((fileName) => {
// Remove ".md" from file name to get id
const id = fileName.replace(/.md$/, '');
// Read markdown file as string
const fullPath = path.join(docsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents);
// Combine the data with the id
return {
id,
...(matterResult.data as {
title: string;
description: string;
}),
};
});
// Sort posts by date
return allDocsData.sort(({ date: a }, { date: b }) => {
if (a <b> b) {
return -1;
}
return 0;
});
}
export async function getDocData(id: string) {
const fullPath = path.join(docsDirectory, `${id}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents);
// Use remark to convert markdown into HTML string
const processedContent = await remark()
.use(html)
.use(prism)
.use(slug)
.use(autolinkHeadings)
.process(matterResult.content);
const contentHtml = processedContent.toString();
// Combine the data with the id
return {
id,
contentHtml,
...(matterResult.data as {
title: string;
description: string;
}),
};
}
export function getAllDocIds() {
const fileNames = fs.readdirSync(docsDirectory);
return fileNames.map((fileName) => {
return {
params: {
id: fileName.replace(/.md$/, ''),
},
};
});
}
Let’s break down this code:
- Import Statements: Import necessary modules for file system operations, Markdown parsing, and HTML conversion.
- `docsDirectory` Variable: Defines the path to the “docs” directory.
- `getSortedDocsData()`: This function reads all the Markdown files in the “docs” directory, extracts the frontmatter (title, description, etc.), and returns an array of objects containing the file ID and metadata.
- `getDocData(id)`: This function takes a file ID as input, reads the corresponding Markdown file, parses its content, converts the Markdown to HTML using `remark` and its plugins, and returns an object containing the file ID, HTML content, and metadata.
- `getAllDocIds()`: This function retrieves all the file names from the “docs” directory and returns an array of objects, where each object contains a `params` object with the file ID. This is used for dynamic routes.
Creating Dynamic Routes for Documentation Pages
Next.js provides a powerful feature called dynamic routes that allows you to generate pages based on data. We’ll use this to create individual pages for each of our documentation files. Create a new file named `[id].js` inside the `pages/docs` directory. This is the file that will handle the dynamic routes. If the `pages/docs` directory doesn’t exist, create it.
import { useRouter } from 'next/router';
import Head from 'next/head';
import { getDocData, getAllDocIds } from '../../lib/docs';
import styles from '../../styles/Doc.module.css';
export async function getStaticPaths() {
const paths = getAllDocIds();
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const docData = await getDocData(params.id);
return {
props: {
docData,
},
};
}
export default function Doc({ docData }) {
const router = useRouter();
if (router.isFallback) {
return <div>Loading...</div>;
}
return (
<div>
<title>{docData.title}</title>
<article>
<h1>{docData.title}</h1>
<div />
</article>
</div>
);
}
Let’s break down this code:
- Import Statements: Import necessary modules, including the `useRouter` hook, `Head` component, and functions from `lib/docs.js`.
- `getStaticPaths()`: This function is responsible for pre-rendering all the possible paths for our documentation pages. It uses `getAllDocIds()` to get a list of all the file IDs and returns an array of paths. The `fallback: false` option means that any paths not defined in `getStaticPaths` will result in a 404 error.
- `getStaticProps({ params })`: This function fetches the data for each documentation page. It uses the `params.id` to get the file ID and then calls `getDocData()` to retrieve the content and metadata.
- `Doc({ docData })`: This is the main component that renders the documentation page. It receives the `docData` as a prop and displays the title, description, and HTML content. The `dangerouslySetInnerHTML` prop is used to render the HTML content generated from the Markdown.
Creating a Navigation Menu
To make our documentation site navigable, let’s create a navigation menu that lists all the available documentation pages. Create a new component called “Sidebar.js” inside the `components` directory. If the components directory doesn’t exist, create it.
import Link from 'next/link';
import { getSortedDocsData } from '../lib/docs';
import styles from '../styles/Sidebar.module.css';
export default function Sidebar() {
const allDocsData = getSortedDocsData();
return (
<nav>
<ul>
{allDocsData.map(({ id, title }) => (
<li>
{title}
</li>
))}
</ul>
</nav>
);
}
Here’s how this component works:
- Import Statements: Import `Link` from `next/link` for internal navigation and `getSortedDocsData` from `../lib/docs`.
- `getSortedDocsData()`: This function fetches the data for all documentation pages.
- Mapping through the data: The `map` function iterates over the `allDocsData` array and renders a `
- ` element for each documentation page. Each `
- ` contains a `Link` component that points to the corresponding page.
Now, let’s incorporate the sidebar into our main layout. Open the `pages/_app.js` file and modify it as follows:
import '../styles/globals.css';
import Sidebar from '../components/Sidebar';
import styles from '../styles/Layout.module.css';
function MyApp({ Component, pageProps }) {
return (
<div>
<main>
</main>
</div>
);
}
export default MyApp;
This code imports the `Sidebar` component and places it alongside the main content area.
Styling Your Documentation Site
To enhance the visual appeal of your documentation site, let’s add some styling. Create two CSS modules, one for the general layout and one for the documentation pages. Create the following files inside the `styles` directory:
- Layout.module.css:
.container {
display: flex;
min-height: 100vh;
}
.main {
flex: 1;
padding: 20px;
}
- Sidebar.module.css:
.sidebar {
width: 200px;
padding: 20px;
background-color: #f0f0f0;
border-right: 1px solid #ccc;
}
.sidebar ul {
list-style: none;
padding: 0;
}
.sidebar li {
margin-bottom: 10px;
}
.sidebar a {
text-decoration: none;
color: #333;
display: block;
padding: 5px;
border-radius: 4px;
}
.sidebar a:hover {
background-color: #ddd;
}
- Doc.module.css:
.container {
padding: 20px;
}
.headingLg {
font-size: 2rem;
margin-bottom: 1rem;
}
These CSS modules provide basic styling for the layout, sidebar, and documentation pages. You can customize the styles to match your design preferences.
Testing and Deployment
Before deploying your documentation site, it’s essential to test it thoroughly. Run the following command in your terminal to start the development server:
npm run dev
This will start the development server, and you can access your documentation site in your browser at `http://localhost:3000`. Test the navigation, content rendering, and styling to ensure everything works as expected.
Once you’re satisfied with your documentation site, you can deploy it to a hosting platform like Vercel or Netlify. These platforms provide seamless deployment and hosting for Next.js applications. To deploy to Vercel, simply push your code to a Git repository (e.g., GitHub, GitLab, or Bitbucket) and import the repository into Vercel. Vercel will automatically build and deploy your application.
Common Mistakes and How to Fix Them
Here are some common mistakes developers make when building documentation sites with Next.js and how to fix them:
- Incorrect File Paths: Ensure that your file paths in the `lib/docs.js` file and the `[id].js` file are correct. Double-check that you’re pointing to the right directories and files.
- Markdown Parsing Errors: If your Markdown content isn’t rendering correctly, check for syntax errors in your Markdown files. Also, verify that you’ve installed all the necessary Markdown processing libraries (gray-matter, remark, remark-html, etc.) and that you’re using them correctly in your code.
- Missing Dependencies: Make sure you have installed all the required dependencies. Run `npm install` in your project directory to ensure all dependencies are installed.
- Incorrect CSS Module Imports: Verify that you’re importing the correct CSS modules and applying the classes to the appropriate elements.
- Caching Issues: During development, you may encounter caching issues. Try clearing your browser cache or restarting the development server to see if that resolves the problem.
Key Takeaways
- Next.js is an excellent framework for building documentation sites due to its performance, SEO capabilities, and developer-friendly features.
- Markdown is an ideal format for writing documentation content because it’s simple, readable, and easy to maintain.
- Dynamic routes in Next.js make it easy to create individual pages for each documentation file.
- CSS modules help you organize and style your documentation site.
- Testing and deployment are critical steps in ensuring your documentation site works correctly and is accessible to users.
FAQ
- Can I use different Markdown processors with Next.js? Yes, you can. While this tutorial uses `remark`, you can integrate with other Markdown processors like `markdown-it` if you prefer.
- How can I add code highlighting to my documentation? The example code uses the `remark-prism` plugin for code highlighting. You can install it and configure it to highlight code blocks in your Markdown files.
- How do I add a table of contents to my documentation? You can generate a table of contents by parsing the Markdown content and extracting the headings. Then, you can use the heading data to create a table of contents component.
- Can I use images in my Markdown files? Yes, you can. You can include images in your Markdown files using the standard Markdown image syntax (``). Make sure your images are accessible and correctly referenced.
- How can I make my documentation site responsive? You can use CSS media queries to make your documentation site responsive and adapt to different screen sizes.
By following these steps, you can create a robust and user-friendly documentation site that helps you effectively communicate your ideas and projects. Remember to continuously update and refine your documentation as your project evolves, ensuring that it remains a valuable resource for your users. Building a documentation site is not just about writing; it’s about providing a clear path for others to understand and utilize your work effectively. The effort invested in well-crafted documentation invariably pays off, fostering a more informed and engaged audience, and ultimately, contributing to the success of your project.
