Building a Simple Blog with Next.js and Strapi
ABHAY THAKOR
Jan 11, 2026 • 3 min read
Next.js and Strapi make a solid pair when you want a flexible, developer-friendly blog setup. Next.js handles the frontend and rendering, while Strapi gives you a customizable backend and content API.
This post walks through a minimal setup that you can expand later.
Why This Stack Works
A few reasons developers keep coming back to this combo:
- Next.js gives you server-side rendering, static generation, and great performance.
- Strapi is easy to self-host, schema-driven, and lets you customize content models without a lot of ceremony.
- Both tools have strong communities and decent docs.
Step 1: Set Up Strapi
Start by creating a new Strapi project:
npx create-strapi-app my-blog-backend --quickstart
Once it boots, open the admin panel and create a Post collection type with fields like:
- title (Text)
- slug (UID)
- description (Text)
- content (Rich Text)
- publishedAt (Date)
Make sure to enable public read access for posts in Settings → Roles → Public.
Step 2: Add Some Sample Content
Before touching the frontend, create 2–3 posts in Strapi. This helps you test your API responses and edge cases.
Take note of your API endpoint, which should look like:
http://localhost:1337/api/posts
Step 3: Create the Next.js App
Now spin up a new Next.js project:
npx create-next-app my-blog-frontend
cd my-blog-frontend
npm run dev
Add an environment variable for your Strapi base URL:
NEXT_PUBLIC_API_URL=http://localhost:1337
Step 4: Fetch Posts on the Home Page
Update pages/index.js to fetch posts at build time:
export async function getStaticProps() {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/posts`);
const data = await res.json();
return {
props: {
posts: data.data,
},
revalidate: 60,
};
}
export default function Home({ posts }) {
return (
<main>
<h1>My Blog</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/posts/${post.attributes.slug}`}>
{post.attributes.title}
</a>
</li>
))}
</ul>
</main>
);
}
This gives you a basic list of blog posts.
Step 5: Add Dynamic Routes
Create a file at pages/posts/[slug].js to render individual posts.
Fetch the available slugs:
export async function getStaticPaths() {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/posts`);
const data = await res.json();
const paths = data.data.map((post) => ({
params: { slug: post.attributes.slug },
}));
return { paths, fallback: false };
}
Then fetch the post content:
export async function getStaticProps({ params }) {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/posts?filters[slug][$eq]=${params.slug}`
);
const data = await res.json();
return {
props: {
post: data.data[0],
},
};
}
Render it:
export default function Post({ post }) {
return (
<article>
<h1>{post.attributes.title}</h1>
<p>{post.attributes.description}</p>
<div
dangerouslySetInnerHTML={{
__html: post.attributes.content,
}}
/>
</article>
);
}
Step 6: Handle Markdown or Rich Text
Strapi’s rich text field returns HTML. If you prefer Markdown:
- Use a Markdown editor plugin in Strapi.
- Parse Markdown in Next.js with a library like
remark.
This keeps your content cleaner and more portable.
What to Improve Next
Once this baseline works, consider:
- Adding pagination or infinite scroll.
- Caching API responses with SWR or React Query.
- Using ISR for near-real-time updates.
- Styling with Tailwind or CSS Modules.
Final Thoughts
This setup gives you a clean separation between content and presentation. You can iterate on either side without breaking the other.
If you keep your Strapi models simple and your Next.js pages predictable, this stack scales nicely from a personal blog to something more production-ready.