如何在NextJS中实现服务端渲染的多分组可折叠全局侧边导航栏
实现Next.js SSR侧边导航栏方案
核心原则
SEO优先的话,导航的所有静态内容(包括子分组)必须在服务端渲染输出,客户端只处理下拉展开/收起的交互逻辑——这样爬虫能抓取到完整导航结构,同时保留前端交互性。
具体实现步骤
1. 构建纯服务端渲染的导航结构组件
先写一个不需要'use client'的组件,把所有导航分组、子分组的HTML结构都在服务端渲染出来,子分组默认用CSS隐藏。
示例代码:
// components/ServerSideNav.tsx import Link from 'next/link'; // 导航数据(可从API/数据库拉取,服务端执行) const navData = [ { title: '产品', items: [ { label: '产品1', href: '/products/1' }, { label: '产品2', href: '/products/2' } ] }, { title: '文档', items: [ { label: '快速开始', href: '/docs/quickstart' }, { label: 'API参考', href: '/docs/api' } ] } ]; export default function ServerSideNav() { return ( <aside className="w-64 bg-gray-100 p-4"> <nav> {navData.map((group, idx) => ( <div key={idx} className="mb-4"> {/* 分组标题+下拉箭头(静态元素,交互后续添加) */} <div className="flex items-center justify-between cursor-pointer group-header"> <h3 className="font-semibold">{group.title}</h3> <span className="arrow">▼</span> </div> {/* 子分组:默认隐藏 */} <ul className="pl-4 mt-2 hidden sub-menu"> {group.items.map((item, itemIdx) => ( <li key={itemIdx} className="mb-2"> <Link href={item.href} className="text-gray-700 hover:text-blue-600"> {item.label} </Link> </li> ))} </ul> </div> ))} </nav> </aside> ); }
这个组件完全由服务端渲染,所有导航内容都会输出到HTML中,爬虫能完整抓取,满足SEO需求。
2. 拆分交互逻辑到客户端组件
把下拉展开/收起的交互单独做成带'use client'的客户端组件,只负责控制DOM状态,不影响导航结构的SSR。
示例代码:
// components/NavToggle.tsx 'use client'; import { useEffect } from 'react'; export default function NavToggle() { useEffect(() => { // 绑定分组标题点击事件 const headers = document.querySelectorAll('.group-header'); headers.forEach(header => { header.addEventListener('click', () => { const subMenu = header.nextElementSibling; subMenu?.classList.toggle('hidden'); // 切换箭头方向 const arrow = header.querySelector('.arrow'); if (arrow) arrow.textContent = arrow.textContent === '▼' ? '▲' : '▼'; }); }); // 清理事件监听 return () => { headers.forEach(header => header.removeEventListener('click', () => {})); }; }, []); // 仅执行交互逻辑,无需渲染内容 return null; }
3. 整合组件并全局挂载
在layout.tsx(Next.js 13+ App Router)或_app.tsx(Pages Router)中引入两个组件,让导航在所有页面显示:
App Router 示例(layout.tsx)
// app/layout.tsx import ServerSideNav from '@/components/ServerSideNav'; import NavToggle from '@/components/NavToggle'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="zh-CN"> <body className="flex"> <ServerSideNav /> <main className="flex-1 p-6">{children}</main> {/* 挂载交互组件,仅执行一次 */} <NavToggle /> </body> </html> ); }
Pages Router 示例(_app.tsx)
// pages/_app.tsx import ServerSideNav from '../components/ServerSideNav'; import NavToggle from '../components/NavToggle'; import '../styles/globals.css'; function MyApp({ Component, pageProps }) { return ( <div className="flex"> <ServerSideNav /> <main className="flex-1 p-6"> <Component {...pageProps} /> </main> <NavToggle /> </div> ); } export default MyApp;
4. 样式优化(可选)
用自定义CSS或Tailwind美化交互效果:
/* globals.css */ .group-header:hover { color: #3b82f6; } .sub-menu { transition: height 0.2s ease; }
关键说明
- 为什么不做全客户端组件?全客户端组件的内容是浏览器渲染的,爬虫抓不到,会严重影响SEO;本方案中所有导航链接和文本都在服务端渲染到HTML中,爬虫能完整读取,同时客户端仅处理交互逻辑。
- 如果导航数据需要从API获取,直接在
ServerSideNav组件中用fetch(App Router默认服务端执行)或getServerSideProps(Pages Router),均为服务端操作,不影响SSR。
内容的提问来源于stack exchange,提问作者Vineeth Vishwanath
相关产品推荐
相关产品推荐

