Next.js 404 on Dynamic Routes Fix
In this tutorial, you'll learn about Next.js 404 on Dynamic Routes Fix. We cover key concepts, practical examples, and best practices.
The Problem
Visiting a dynamic route like /posts/my-slug returns a 404 page even though the route is defined. Dynamic routes in Next.js require specific file naming and proper path generation.
Quick Fix
Step 1: Verify file naming convention
Dynamic route files must use square brackets:
pages/
├── posts/
│ └── [slug].jsx // Right
│ └── slug.jsx // Wrong — static route, not dynamic
Expected output: Next.js recognizes [slug].jsx as a dynamic route.
Step 2: Implement getStaticPaths for SSG
// Wrong — missing getStaticPaths
export default function Post({ post }) {
return <article>{post.title}</article>;
}
// Right
export async function getStaticPaths() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return {
paths: posts.map(post => ({
params: { slug: post.slug },
})),
fallback: 'blocking',
};
}
export async function getStaticProps({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(r => r.json());
return { props: { post } };
}
Expected output: Each post is accessible at /posts/{slug}.
Step 3: Handle fallback modes correctly
// fallback: false — 404 for unknown paths
// fallback: true — show fallback UI, then render on client
// fallback: 'blocking' — SSR on first request, then cache
export async function getStaticPaths() {
return {
paths: [{ params: { slug: 'hello-world' } }],
fallback: 'blocking', // recommended for SEO
};
}
Expected output: Unknown paths are handled based on the fallback strategy.
Step 4: Use getServerSideProps for SSR-only routes
export async function getServerSideProps({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(r => {
if (!r.ok) return { notFound: true };
return r.json();
});
return { props: { post } };
}
Expected output: Dynamic routes render on the server and never return 404.
Step 5: Check for catch-all routes
// pages/posts/[...slug].jsx — catches /posts/a, /posts/a/b, etc.
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking',
};
}
export async function getStaticProps({ params }) {
const slug = params.slug.join('/');
// handle multi-segment slug
}
Expected output: Multi-segment paths like /posts/2024/jan/update are handled.
Step 6: Return notFound from getStaticProps
export async function getStaticProps({ params }) {
try {
const post = await fetch(`/api/posts/${params.slug}`);
if (!post.ok) return { notFound: true };
return { props: { post } };
} catch {
return { notFound: true };
}
}
Expected output: Invalid slugs return a proper 404 page.
Prevention
- Use square brackets for dynamic route files
- Always implement
getStaticPathsfor SSG dynamic routes - Use
fallback: 'blocking'for SEO with dynamic content - Return
{ notFound: true }from data fetching functions for invalid routes
Common Mistakes with 404 page
- Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
- Non-exhaustive pattern matches that compile with warnings then crash at runtime
- Misunderstanding that
Stringis[Char]with poor performance for large text operations
These mistakes appear frequently in real-world NEXTJS code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro