You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 22:15:27