Learn how to build a static website using Next.js App Router and Fusionable. This guide covers Markdown content, dynamic filtering, and rendering with Showdown for a flexible, fast static site.
In this guide, we’ll use Next.js App Router (introduced in Next.js 13) and Fusionable to create a static website with Markdown-based content. The App Router provides a modern approach to building React applications with layouts, server components, and streamlined data fetching.
What we'll cover:
- Setting up Next.js and Fusionable
- Organizing Markdown content
- Loading and filtering content with Fusionable
- Rendering Markdown with Showdown
- Using the App Router for dynamic routes
If you want to provide feedback, give a star, ask a feature request, or know more, here is the open-source repository of Fusionable https://github.com/Hi-Folks/fusionable
The tools/features we are going to use
To build our sample (and simple) website, we are going to use these tools:
- Next.js is a powerful React framework for building fast and scalable web applications. It provides features like server-side rendering (SSR), static site generation (SSG), and API routes, making it ideal for modern web development.
- The App Router in Next.js (introduced in version 13) is a modern way to organize your application using React Server Components (RSC). It simplifies layouts, data fetching, and routing while improving performance and reducing client-side JavaScript.
- Fusionable is a JavaScript library for managing and querying content. It supports structured data querying (filtering, sorting, and limiting) and is perfect for static websites with Markdown-based content or other lightweight data sources. More info here: https://github.com/Hi-Folks/fusionable
- Showdown is a versatile library that converts Markdown into HTML. It’s useful for rendering rich content on static websites by transforming plain text Markdown files into fully formatted HTML.
Set Up Your NEXT.js Project
Start by creating a new Next.js project and installing Fusionable and Showdown.
# Create a new Next.js app with TypeScript
bunx create-next-app@14 my-nextjs-site --typescript
# Install dependencies
cd my-nextjs-site
bun add fusionable showdown
If you prefer to use npm:
# Create a new Next.js app with TypeScript
npx create-next-app@14 my-nextjs-site --typescript
# Install dependencies
cd my-nextjs-site
bun install fusionable showdown
For the purpose of this tutorial, we want to keep the app minimal so I used this options:
✔ Would you like to use ESLint? No
✔ Would you like to use Tailwind CSS? No
✔ Would you like to use src/
directory? Yes
✔ Would you like to use App Router? (recommended) Yes
✔ Would you like to customize the default import alias (@/*)? No
Organize Your Markdown Content
Create a directory for your Markdown files. For example:
mkdir -p content/posts
Add a sample Markdown post in content/posts:
# content/posts/my-first-post.md
---
title: "My First Post"
date: "2023-10-01"
slug: "my-first-post"
highlight: true
---
This is an example **Markdown** post.
Each post contains frontmatter metadata (title
, date
, slug
, highlight
) and the Markdown content.
Create a Homepage to List Posts
In the App Router, define a homepage component in src/app/page.tsx
.
We will implement the getPosts
function to fetch and render all posts using Fusionable.
Fusionable simplifies loading and querying Markdown files. https://github.com/Hi-Folks/fusionable
// app/page.tsx
import Link from 'next/link';
import FusionCollection from 'fusionable/FusionCollection';
function getPosts() {
const collection = new FusionCollection()
.loadFromDir('content/posts')
.orderBy('date', 'desc');
return collection.getItemsArray();
}
export default function HomePage() {
const posts = getPosts(); // Static generation by default
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.fields.slug}>
<Link href={`/posts/${post.fields.slug}`}>
{post.fields.title}
</Link>
<p>{post.fields.date}</p>
</li>
))}
</ul>
</main>
);
}
Create Dynamic Post Pages
In the App Router, dynamic routes are handled using [slug] folders. Create the following structure:
src/
app/
posts/
[slug]/
page.tsx
In src/app/posts/[slug]/page.tsx
, we are going to write the code for loading and rendering a single post.
// app/posts/[slug]/page.tsx
import FusionCollection from "fusionable/FusionCollection";
import Showdown from 'showdown';
function getPostBySlug(slug: string) {
const collection = new FusionCollection().loadFromDir('content/posts');
const post = collection.getOneBySlug(slug);
if (!post) {
throw new Error('Post not found');
}
return post.getItem();
}
export default function PostPage({ params }: { params: { slug: string } }) {
const post = getPostBySlug(params.slug); // Static generation by default
const converter = new Showdown.Converter();
const contentHTML = converter.makeHtml(post.content);
return (
<article>
<h1>{post.title}</h1>
<p>{post.date}</p>
<div dangerouslySetInnerHTML={{ __html: contentHTML }} />
</article>
);
}
Convert Markdown to HTML with Showdown
In the src/app/posts/[slug]/page.tsx
file, we use Showdown to convert Markdown content to HTML dynamically:
import Showdown from 'showdown';
// Initialize Showdown and convert Markdown content
const converter = new Showdown.Converter();
const contentHTML = converter.makeHtml(post.content);
The resulting contentHTML
is rendered with dangerouslySetInnerHTML
for proper HTML display.
Final Touch: Full Folder Structure
Here’s your project’s folder structure after implementing the above steps:
my-nextjs-site/
├── src/app/
│ ├── page.tsx
│ ├── posts/
│ │ ├── [slug]/
│ │ │ ├── page.tsx
├── content/
│ ├── posts/
│ │ ├── my-first-post.md
Wrapping Up
With Next.js App Router and Fusionable, you’ve built a fully functional static site with:
- Markdown-based content: Easy to write and manage
- Fusionable queries: For filtering and sorting content
- Showdown integration: For converting Markdown to HTML
- Static generation: Fast and SEO-friendly
You can now expand this project with categories, tags, or additional filters to make it even more dynamic. Have fun building!
Top comments (1)
If you are more interested in Svelte and SvelteKit, here is the article: dev.to/robertobutti/how-to-build-a...