如何在Next.js中实现三类/blog动态路由并兼容重复slug
解决Next.js中/blog/postname路由的重复slug问题
方案一:服务端校验后重定向/返回列表
针对重复slug的情况,在/blog/[slug]页面的服务端逻辑中先查询该slug对应的所有文章:
- 若仅存在一篇,直接渲染文章内容;
- 若存在多篇,自动重定向到其中一篇的完整分类路由(如
/blog/category/postname),或返回一个列表页面让用户选择具体文章。
示例代码(Pages Router + getServerSideProps):
// pages/blog/[slug].js export async function getServerSideProps(context) { const { slug } = context.params; // 替换为实际的数据库查询逻辑 const posts = await fetch(`/api/posts?slug=${slug}`).then(res => res.json()); if (posts.length === 1) { return { props: { post: posts[0] } }; } else if (posts.length > 1) { // 临时重定向到第一篇文章的完整路由 return { redirect: { destination: `/blog/${posts[0].category}/${slug}`, permanent: false } }; } else { return { notFound: true }; } } export default function PostPage({ post }) { return ( <div> <h1>{post.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.content }} /> </div> ); }
方案二:给slug附加唯一标识符(兼顾可读性与唯一性)
给重复的slug拼接文章唯一ID(如数据库主键),生成/blog/[slug]-[postId]形式的路由,同时保留原slug路由作为别名,自动重定向到带ID的唯一路由。
示例代码(Pages Router + getStaticPaths):
// pages/blog/[slug].js export async function getStaticPaths() { const posts = await fetch('/api/posts').then(res => res.json()); // 生成两种路径:带ID的唯一路由、原slug路由 const paths = posts.flatMap(post => [ { params: { slug: `${post.slug}-${post.id}` } }, { params: { slug: post.slug } } ]); return { paths, fallback: 'blocking' }; } export async function getStaticProps(context) { const { slug } = context.params; // 判断slug是否包含ID const hasId = slug.includes('-'); let post; if (hasId) { const [, postId] = slug.split('-'); post = await fetch(`/api/posts/${postId}`).then(res => res.json()); } else { const posts = await fetch(`/api/posts?slug=${slug}`).then(res => res.json()); if (posts.length > 0) { // 重定向到带ID的唯一路由 return { redirect: { destination: `/blog/${posts[0].slug}-${posts[0].id}`, permanent: true } }; } else { return { notFound: true }; } } return { props: { post }, revalidate: 60 }; }
方案三:利用中间件统一处理路由(Next.js 13+ App Router)
在App Router项目中,通过middleware.js拦截/blog/[slug]请求,提前校验slug对应的文章数量,若存在重复则重定向到完整分类路由。
示例代码:
// middleware.js import { NextResponse } from 'next/server'; export async function middleware(request) { const pathname = request.nextUrl.pathname; const slugMatch = pathname.match(/^\/blog\/([^/]+)$/); if (slugMatch) { const slug = slugMatch[1]; // 替换为实际的数据库查询逻辑 const posts = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/posts?slug=${slug}`).then(res => res.json()); if (posts.length > 1) { // 重定向到第一篇文章的完整分类路由 return NextResponse.redirect(new URL(`/blog/${posts[0].category}/${slug}`, request.url)); } } return NextResponse.next(); } export const config = { matcher: '/blog/:slug*' };
内容的提问来源于stack exchange,提问作者Karthik
相关产品推荐
相关产品推荐

