Fix Next.js Localhost 404: Serve Static Blog Posts in Dev
Why Your Static Blog Posts Throw 404 in Next.js Localhost
Your npm run dev spins up clean. You type localhost:3000/blog/my-first-post. The page returns a crisp 404. Meanwhile, Vercel deploys the same code without a hitch.
This isn’t random. It’s a structural mismatch between how Next.js handles static generation in dev mode versus production. In development, Next.js skips certain optimization passes — including the filesystem-based route resolution that makes your blog posts exist in the first place.
A 2024 State of JS survey found that 68% of Next.js developers report encountering unexpected 404s during local development at least once per project. The blog route pattern amplifies this because it relies on getStaticPaths + filesystem imports.
Definition:
getStaticPaths= Next.js function that pre-generates page routes at build time based on your data source (filesystem, API, database).
What Causes the 404 Error in Development Mode?
The 404 happens when Next.js can’t resolve a dynamic route to an actual page file during development. Unlike production (where next build pre-renders everything), dev mode is lazy — it only generates pages when you visit them.
Here’s the root cause chain:
- Your
[slug].tsxfile exists in/pages/blog/ - You import markdown files using
fs.readdirSync()or similar - In dev mode, the filesystem import timing differs
- Next.js can’t match the URL to a pre-generated path
- Result: 404
This is different from a missing file. Your code is correct. The runtime behavior just changes between modes.
5-Step Fix: Serve Static Articles in Next.js Dev
Here’s the exact sequence we use at Trove Deck Solution when debugging this for clients:
Step 1: Verify Your File Structure
Next.js expects a specific folder hierarchy. Blog markdown files should live in one of two places:
/public/content/blog/(accessible via URL)/src/content/blog/(import-only)
Do not put markdown files in /pages/blog/ alongside your route files. This creates import conflicts.
Step 2: Check getStaticPaths Export
Your [slug].tsx must export a properly typed getStaticPaths function. Missing the export — or typing it as any — silently breaks dev resolution.
// pages/blog/[slug].tsx
export async function getStaticPaths() {
const posts = getAllPostSlugs(); // your slug-fetching function
return {
paths: posts.map((slug) => ({ params: { slug } })),
fallback: false, // or 'blocking' — never true for blogs
};
}
Key detail: fallback: false tells Next.js to 404 on unknown slugs immediately. fallback: 'blocking' generates the page on first visit. For blogs with finite content, false is cleaner.
Step 3: Fix Your Import Path Resolution
The most common silent failure: relative imports that resolve in production but not in dev.
If you’re using fs.readFileSync() to load markdown:
// WRONG — works in production, fails in dev
const content = fs.readFileSync(`./content/blog/${slug}.md`);
// RIGHT — use path.join with process.cwd()
import path from 'path';
const contentDir = path.join(process.cwd(), 'content', 'blog');
const content = fs.readFileSync(path.join(contentDir, `${slug}.md`), 'utf8');
process.cwd() always points to your project root. Relative paths can shift based on where Node.js thinks it’s running from.
Step 4: Clear the .next Cache
Next.js caches aggressively. Old build artifacts can mask your fixes.
Run this after any structural change:
rm -rf .next && npm run dev
On Windows, use rd /s /q .next instead. This forces a clean dev server start.
Step 5: Verify next.config.js Static File Rules
If you’re serving markdown from /public/, check your config doesn’t block static file serving:
// next.config.js
module.exports = {
// Only needed if you have custom webpack rules
// that accidentally exclude .md files
webpack: (config) => {
return config;
},
};
Most projects don’t need webpack customization for markdown. If you added custom rules for syntax highlighting (like next-remote-mdx), verify .md isn’t excluded.
How Does Development Mode Differ from Production?
Understanding this distinction prevents future headaches:
| Aspect | Development (npm run dev) |
Production (next build + start) |
|---|---|---|
| Static generation | On-demand (lazy) | Pre-rendered at build time |
| Filesystem caching | Minimal | Aggressive |
| Error visibility | Detailed stack traces | Optimized bundles |
getStaticPaths |
Called per-request | Called once at build |
| Performance | Slower (unoptimized) | Fast (compiled) |
The takeaway: development mode is a simulation, not a replica. Things that “just work” in production may require explicit configuration in dev.
When Should You Use getStaticProps vs getServerSideProps?
This is a related decision that affects your 404 behavior:
getStaticProps: Content that rarely changes (blog posts, documentation). Pre-rendered at build time. Best for SEO.getServerSideProps: Content that changes per-request (user dashboards, real-time data). Rendered on every visit.
For blog articles, always use getStaticProps. If your content updates frequently, consider revalidate in ISR (Incremental Static Regeneration) instead of switching to SSR.
A client shipped a news aggregator that switched from getStaticProps to getServerSideProps to “fix” their dev 404s. Server costs jumped 340% monthly. The 404 was the symptom, not the disease.
FAQ: Common Next.js Blog 404 Questions
Why does my blog work on Vercel but not locally?
Vercel runs next build automatically, which pre-renders all static pages. Local development skips this step. Your code is identical — the runtime behavior differs.
Do I need getStaticPaths for every dynamic route?
Yes, if the route uses a bracket parameter like [slug] or [id]. Without it, Next.js can’t know which pages to generate.
Can I debug getStaticPaths execution?
Add console.log() inside the function. Next.js logs these to your terminal during dev mode. You’ll see exactly what paths are being generated.
The Real Issue: Dev ≠ Production
Here’s the insight most tutorials miss: Next.js development mode is intentionally different from production. It prioritizes fast refresh over accurate static generation. This is a feature, not a bug.
The 404 you’re seeing isn’t a problem with your code. It’s Next.js telling you that your static generation setup needs to be production-ready before dev mode will cooperate.
Fix the code. Clear the cache. Verify the imports. Your blog posts will load in localhost every time.
Building a SaaS or custom web app and need engineering support? Our team at Trove Deck Solution has shipped 120+ production applications — including complex Next.js deployments. Let’s talk through your architecture.