基于Next.js将静态网站转为动态站,实现无头CMS驱动的动态导航方案
最优实现方案:静态站转Next.js+Strapi动态站点(导航自动更新)
针对你的需求,核心思路是用Strapi统一管理所有页面元数据与导航结构,Next.js通过API拉取数据动态渲染页面和导航,实现新增页面时导航自动同步。以下是分步实现细节:
一、Strapi数据模型设计
先在Strapi中搭建支撑动态导航和页面的核心模型:
- NavigationItem模型:管理导航菜单的每一项,字段包括:
title:导航显示文本slug:对应页面的路由路径parent:关联自身模型(可选),用于实现多级菜单(比如「公司项目」下的分类)isVisible:布尔值,控制是否在导航栏显示sortOrder:数字,控制导航项的排序优先级
- Page模型:对应每个页面的具体内容,字段包括:
slug:和NavigationItem的slug一一对应,确保路由匹配title:页面标题content:富文本,存储页面主体内容pageType:枚举值(如home/about/project-category/news/job-form/contact-form),用于Next.js区分渲染不同组件navigationItem:关联NavigationItem模型,确保页面和导航项绑定
- 特殊表单模型:针对招聘/联系表单,可单独创建
JobApplication和ContactMessage模型,用于存储用户提交的表单数据。
二、Next.js动态导航实现
在Next.js中构建可自动更新的导航组件,核心是从Strapi拉取并处理导航数据:
- 创建全局导航组件,可放在
components/Navigation.js中:
import { useEffect, useState } from 'react'; export default function Navigation() { const [navItems, setNavItems] = useState([]); useEffect(() => { const fetchNavData = async () => { // 拉取已启用的导航项,按排序优先级排序,关联父级数据 const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/navigation-items?filters[isVisible][$eq]=true&sort=sortOrder:asc&populate=parent`); const { data } = await res.json(); // 整理多级菜单结构,将子项挂载到父项的children属性 const navMap = {}; data.forEach(item => { const navItem = { id: item.id, title: item.attributes.title, slug: item.attributes.slug, parentId: item.attributes.parent?.data?.id }; if (navItem.parentId) { navMap[navItem.parentId] = navMap[navItem.parentId] || { ...navMap[navItem.parentId], children: [] }; navMap[navItem.parentId].children.push(navItem); } else { navMap[item.id] = { ...navItem, children: [] }; } }); setNavItems(Object.values(navMap)); }; fetchNavData(); }, []); // 递归渲染多级导航菜单 const renderNav = (items) => ( <ul className="nav-list"> {items.map(item => ( <li key={item.id} className="nav-item"> <a href={`/${item.slug}`}>{item.title}</a> {item.children.length > 0 && <ul className="sub-nav">{renderNav(item.children)}</ul>} </li> ))} </ul> ); return <nav className="site-nav">{renderNav(navItems)}</nav>; }
- 将导航组件引入
_app.js或全局layout中,确保所有页面共享同一导航。 - 性能优化:若使用静态生成,可在
getStaticProps中预取导航数据并传递给组件;或用SWR/React Query缓存导航数据,减少重复请求。
三、动态页面路由与渲染
利用Next.js的动态路由实现页面的自动生成:
- 创建
pages/[slug].js作为所有动态页面的入口:
import HomePage from '../components/HomePage'; import AboutPage from '../components/AboutPage'; import ProjectCategoryPage from '../components/ProjectCategoryPage'; import NewsPage from '../components/NewsPage'; import JobFormPage from '../components/JobFormPage'; import ContactFormPage from '../components/ContactFormPage'; // 预生成已存在的页面路由 export async function getStaticPaths() { const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/pages?fields[0]=slug`); const { data } = await res.json(); const paths = data.map(page => ({ params: { slug: page.attributes.slug } })); return { paths, fallback: 'blocking' // 新页面自动触发静态生成,避免404 }; } // 根据slug拉取对应页面数据 export async function getStaticProps({ params }) { const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/pages?filters[slug][$eq]=${params.slug}&populate=*`); const { data } = await res.json(); const page = data[0]; if (!page) return { notFound: true }; return { props: { pageData: page.attributes }, revalidate: 60 // 每60秒自动验证更新内容 }; } // 根据pageType渲染对应组件 export default function DynamicPage({ pageData }) { switch(pageData.pageType) { case 'home': return <HomePage content={pageData.content} />; case 'about': return <AboutPage content={pageData.content} />; case 'project-category': return <ProjectCategoryPage content={pageData.content} />; case 'news': return <NewsPage content={pageData.content} articles={pageData.newsArticles?.data} />; case 'job-form': return <JobFormPage />; case 'contact-form': return <ContactFormPage />; default: return <div className="page-not-found">页面不存在</div>; } }
- 针对新闻文章这类子内容,可在Strapi中创建
NewsArticle模型并关联到Page模型的newsArticles字段,在Next.js的新闻页面中渲染文章列表。
四、表单提交处理
招聘和联系表单通过Next.js API Routes实现后端逻辑:
- 创建
pages/api/submit-job.js处理求职表单提交:
export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ message: '仅支持POST请求' }); } try { const { name, email, resume, message } = req.body; // 调用Strapi API存储求职数据 const strapiRes = await fetch(`${process.env.STRAPI_API_URL}/job-applications`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.STRAPI_API_TOKEN}` }, body: JSON.stringify({ data: { name, email, resume, message } }) }); if (!strapiRes.ok) throw new Error('提交失败'); res.status(200).json({ message: '求职申请提交成功' }); } catch (err) { res.status(500).json({ message: err.message }); } }
- 在表单组件中调用该API完成提交,类似逻辑可复用在联系表单上。
五、进阶优化
- 实时更新:在Strapi中配置Webhook,当导航或页面数据变更时,调用Next.js的
revalidateAPI,触发对应页面的重新生成,无需等待ISR时间。 - 环境变量:将Strapi API地址、令牌等敏感信息存入环境变量(
.env.local),避免硬编码。 - 缓存策略:用SWR/React Query对页面和导航数据做缓存,提升前端加载速度。
内容的提问来源于stack exchange,提问作者7usien
相关产品推荐
相关产品推荐

