如何在Next.js中正确处理带动态路由的无/1后缀SSG分页?
实现无/1后缀的分类SSG分页方案
你可以通过抽离共用渲染组件+拆分路由文件的方式实现需求,既不用复制代码,也能保持URL的简洁性,具体步骤如下:
1. 抽离共用页面模板
先把分类页面的渲染逻辑封装成独立组件,所有路由页面都复用这个组件来渲染内容,避免代码重复。
创建components/CategoryContent.jsx:
export default function CategoryContent({ category, currentPage, posts, totalPages }) { return ( <div> <h1>分类:{category}</h1> <div className="posts-list"> {posts.map(post => ( <article key={post.id}>{post.title}</article> ))} </div> <div className="pagination"> {currentPage > 1 && ( <a href={`/${category}`}>上一页</a> )} {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => ( <a key={page} href={page === 1 ? `/${category}` : `/${category}/${page}`} className={currentPage === page ? 'active' : ''} > {page} </a> ))} {currentPage < totalPages && ( <a href={`/${category}/${currentPage + 1}`}>下一页</a> )} </div> </div> ) }
2. 处理分类首页(无page后缀)
创建pages/[category]/index.jsx,这个文件对应/category-one、/category-two这类首页路径,默认获取第1页的数据:
import CategoryContent from '../../components/CategoryContent' export async function getStaticPaths() { // 假设你从API或数据库获取所有分类列表 const categories = ['category-one', 'category-two'] return { paths: categories.map(category => ({ params: { category } })), fallback: false // 若需要动态生成未预定义的分类,可改为'blocking'或true } } export async function getStaticProps({ params }) { const { category } = params const page = 1 // 首页默认第1页 // 这里替换成你获取分类第1页数据的逻辑 const posts = await fetchPostsByCategory(category, page) const totalPages = await getTotalPagesForCategory(category) return { props: { category, currentPage: page, posts, totalPages } } } export default function CategoryIndexPage(props) { return <CategoryContent {...props} /> }
3. 处理分页页面(带page后缀)
创建pages/[category]/[page].jsx,对应/category-one/2、/category-two/3这类路径,处理page>=2的情况:
import CategoryContent from '../../components/CategoryContent' export async function getStaticPaths() { const categories = ['category-one', 'category-two'] const paths = [] // 为每个分类生成2及以上的分页路径,这里假设每个分类最多有3页 categories.forEach(category => { for (let page = 2; page <= 3; page++) { paths.push({ params: { category, page: page.toString() } }) } }) return { paths, fallback: false } } export async function getStaticProps({ params }) { const { category, page } = params const currentPage = parseInt(page, 10) // 验证page有效性,避免非法路径 if (currentPage < 2) { return { notFound: true } } // 获取对应分页的数据 const posts = await fetchPostsByCategory(category, currentPage) const totalPages = await getTotalPagesForCategory(category) // 如果请求的页数超过总页数,返回404 if (currentPage > totalPages) { return { notFound: true } } return { props: { category, currentPage, posts, totalPages } } } export default function CategoryPage(props) { return <CategoryContent {...props} /> }
关键说明
- 两个路由文件都复用同一个
CategoryContent组件,仅在数据获取逻辑(getStaticPaths和getStaticProps)上做区分,完全避免代码重复。 - 首页默认对应page=1,无需在URL中添加/1后缀,符合你的需求。
- 分页页面仅处理page>=2的情况,非法的page参数(比如1或超过总页数)会返回404,保证路由的合法性。
内容的提问来源于stack exchange,提问作者richtea
相关产品推荐
相关产品推荐

