法登阀门薛珍珍 發表於 2025-10-14 12:55:00

Web典型路由结构之Next.js (App Router, v13+) )(文件系统驱动的路由:File-based Routing)声明式路由:记录即路由

<style>pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; line-height: 1.6 !important; padding: 16px !important; margin: 16px 0 !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; tab-size: 4 !important; -moz-tab-size: 4 !important; max-width: 100% !important; box-sizing: border-box !important }
code { font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; overflow-wrap: normal !important; display: inline !important; background: rgba(0, 0, 0, 0) !important; border: none !important; padding: 0 !important; margin: 0 !important; line-height: inherit !important }
pre code { background: rgba(0, 0, 0, 0) !important; border: 0 !important; border-radius: 0 !important; display: block !important; line-height: 1.6 !important; margin: 0 !important; max-width: none !important; overflow: visible !important; padding: 0 !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; color: inherit !important }
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: rgba(112, 128, 144, 1) !important; font-style: italic !important }
.token.punctuation { color: rgba(153, 153, 153, 1) !important }
.token.atrule, .token.attr-value, .token.keyword { color: rgba(0, 119, 170, 1) !important; font-weight: bold !important }
.token.function, .token.class-name { color: rgba(221, 74, 104, 1) !important; font-weight: bold !important }
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: rgba(102, 153, 0, 1) !important }
.token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: rgba(153, 0, 85, 1) !important }
.cnblogs-markdown pre, .cnblogs-post-body pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; padding: 16px !important; margin: 16px 0 !important }
pre, pre, pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important }</style>
      <div class="markdown_views prism-tomorrow-night" id="content_views"><svg style="display: none" xmlns="http://www.w3.org/2000/svg"><path d="M5,0 0,2.5 5,5z" id="raphael-marker-block" stroke-linecap="round" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0)"></path></svg><p></p><div class="toc"><h4>文章目录</h4><ul><li> 项目结构 (基于 `app` 目录)</li><li> 核心文件实现</li><li><ul><li>1. 根布局 (`app/layout.tsx`)</li><li>2. 首页 (`app/page.tsx`)</li><li>3. 项目列表页 (`app/projects/page.tsx`)</li><li>4. 项目详情页 (`app/projects//page.tsx`)</li><li>5. 设置页权限控制 (`app/settings/layout.tsx`)</li><li>6. 登录页 (`app/signin/page.tsx`)</li><li>7. 404 页面 (`app/not-found.tsx`)</li><li>8. 错误处理 (`app/error.tsx`)</li></ul></li><li> 与 React Router v6 的关键差异</li><li>✅ 为什么 Next.js 的设计更优?</li><li><ul><li>1. **无需维护路由配置**:文件系统即路由,新增页面只需创建文件</li><li>2. **自动代码分割**:按路由自动分割代码块(无需 `lazy`)</li><li>3. **权限控制更合理**:在布局层处理(避免页面加载后再重定向)</li><li>4. **开发体验更好**:文件命名即路由,直观易维护</li><li>5. **内置 404/错误处理**:`not-found.tsx` 和 `error.tsx` 自动生效</li><li>&gt;提示:Next.js 的权限控制**不要**在 `page.tsx` 中做(会先加载页面再重定向),必须放在 `layout.tsx` 中(如 `settings/layout.tsx`)。</li></ul></li><li> 运行效果</li><li>&gt; ✨ **关键总结**:Next.js 的路由是**声明式**的(文件即路由),而 React Router 是**配置式**的(需要手动写路由表)。Next.js 的设计让路由与文件结构绑定,大幅降低维护成本,同时内置了最佳实践(权限、404、动态路由)。</li></ul></div><br> 以下是 Next.js (App Router, v13+) (
SSR)的典型路由结构实现,与 React Router v6(
CSR) 的配置式路由不同,Next.js 采用
<strong>文件系统驱动的路由</strong>(File-based Routing),无需显式编写路由配置文件:
<p></p><hr><h2> 项目结构 (基于 <code>app</code> 目录)</h2>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-bash">app/
├── layout.tsx             <span class="token comment"># 根布局 (所有页面共享)</span>
├── page.tsx               <span class="token comment"># 根路径 / 的页面</span>
├── projects/
│   ├── layout.tsx         <span class="token comment"># 项目模块布局</span>
│   ├── page.tsx         <span class="token comment"># /projects 页面</span>
│   └── <span class="token punctuation">[</span>projectId<span class="token punctuation">]</span>/       <span class="token comment"># 动态路由</span>
│       └── page.tsx       <span class="token comment"># /projects/:projectId</span>
├── settings/
│   ├── layout.tsx         <span class="token comment"># 设置页布局 (用于权限控制)</span>
│   └── page.tsx         <span class="token comment"># /settings 页面</span>
├── signin/
│   └── page.tsx         <span class="token comment"># /signin 页面</span>
├── not-found.tsx          <span class="token comment"># 404 页面 (自定义)</span>
└── error.tsx            <span class="token comment"># 全局错误页面</span></code></pre>
<hr><h2> 核心文件实现</h2><h3>1. 根布局 (<code>app/layout.tsx</code>)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/layout.tsx</span>
<span class="token comment">// 导入全局CSS样式文件(在Next.js中,全局样式通常放在这里)</span>
<span class="token keyword">import</span> <span class="token string">'./globals.css'</span>
<span class="token comment">// 导入Google字体(Inter字体),用于设置页面的字体</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Inter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/font/google'</span>
<span class="token comment">// 使用Inter字体,指定子集为拉丁文(支持大部分英文字符)</span>
<span class="token keyword">const</span> inter <span class="token operator">=</span> <span class="token function">Inter</span><span class="token punctuation">(</span><span class="token punctuation">{</span> subsets<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'latin'</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 设置页面的元数据(标题、描述等,用于SEO优化)</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> metadata <span class="token operator">=</span> <span class="token punctuation">{</span>
title<span class="token operator">:</span> <span class="token string">'Next.js App'</span><span class="token punctuation">,</span> <span class="token comment">// 页面标题</span>
description<span class="token operator">:</span> <span class="token string">'Generated by Next.js'</span><span class="token punctuation">,</span> <span class="token comment">// 页面描述</span>
<span class="token punctuation">}</span>
<span class="token comment">// 根布局组件,所有页面共享这个布局</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">RootLayout</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
children<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span>
children<span class="token operator">:</span> React<span class="token punctuation">.</span>ReactNode <span class="token comment">// 代表子组件(页面内容)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 返回HTML结构,其中包含页面的根元素</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>html lang<span class="token operator">=</span><span class="token string">"en"</span><span class="token operator">&gt;</span> <span class="token comment">// 指定页面语言为英语</span>
<span class="token operator">&lt;</span>body className<span class="token operator">=</span><span class="token punctuation">{</span>inter<span class="token punctuation">.</span>className<span class="token punctuation">}</span><span class="token operator">&gt;</span> <span class="token comment">// 将字体类名应用到body,使页面使用Inter字体</span>
    <span class="token operator">&lt;</span>header<span class="token operator">&gt;</span>My App Header<span class="token operator">&lt;</span><span class="token operator">/</span>header<span class="token operator">&gt;</span> <span class="token comment">// 页面头部(通常包含导航栏)</span>
      <span class="token punctuation">{</span>children<span class="token punctuation">}</span> <span class="token comment">// 这里是子组件(当前页面的内容)将被渲染在这里</span>
      <span class="token operator">&lt;</span>footer<span class="token operator">&gt;</span>Footer<span class="token operator">&lt;</span><span class="token operator">/</span>footer<span class="token operator">&gt;</span> <span class="token comment">// 页面底部</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>body<span class="token operator">&gt;</span>
          <span class="token operator">&lt;</span><span class="token operator">/</span>html<span class="token operator">&gt;</span>
            <span class="token punctuation">)</span>
            <span class="token punctuation">}</span></code></pre>
<hr><h3>2. 首页 (<code>app/page.tsx</code>)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/page.tsx</span>
<span class="token comment">// 首页组件,处理根路径 / 的页面内容</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 返回页面的主要内容</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>main<span class="token operator">&gt;</span> <span class="token comment">// 主要内容区域</span>
<span class="token operator">&lt;</span>h1<span class="token operator">&gt;</span>Welcome to Home<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">&gt;</span> <span class="token comment">// 标题</span>
    <span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/projects"</span><span class="token operator">&gt;</span>Go to Projects<span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span> <span class="token comment">// 链接到项目列表页的链接</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>main<span class="token operator">&gt;</span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">}</span></code></pre>
<hr><h3>3. 项目列表页 (<code>app/projects/page.tsx</code>)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/projects/page.tsx</span>
<span class="token comment">// 项目列表页面组件</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Projects</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 返回项目列表页面的内容</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>div<span class="token operator">&gt;</span> <span class="token comment">// 容器div</span>
<span class="token operator">&lt;</span>h1<span class="token operator">&gt;</span>Projects List<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">&gt;</span> <span class="token comment">// 项目列表标题</span>
    <span class="token operator">&lt;</span>ul<span class="token operator">&gt;</span> <span class="token comment">// 无序列表</span>
      <span class="token operator">&lt;</span>li<span class="token operator">&gt;</span><span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/projects/1"</span><span class="token operator">&gt;</span>Project <span class="token number">1</span><span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span> <span class="token comment">// 项目1的链接</span>
      <span class="token operator">&lt;</span>li<span class="token operator">&gt;</span><span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/projects/2"</span><span class="token operator">&gt;</span>Project <span class="token number">2</span><span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span> <span class="token comment">// 项目2的链接</span>
          <span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
            <span class="token punctuation">)</span>
            <span class="token punctuation">}</span></code></pre>
<hr><h3>4. 项目详情页 (<code>app/projects//page.tsx</code>)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/projects//page.tsx</span>
<span class="token comment">// 项目详情页面组件,用于显示特定项目的详细信息</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> notFound <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/navigation'</span> <span class="token comment">// 用于触发404页面的函数</span>
<span class="token comment">// 项目详情页面组件,接收参数(动态路由参数)</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">ProjectDetail</span><span class="token punctuation">(</span><span class="token punctuation">{</span> params <span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span> params<span class="token operator">:</span> <span class="token punctuation">{</span> projectId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 模拟项目数据(实际应用中会从API获取)</span>
<span class="token keyword">const</span> validProjects <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'1'</span><span class="token punctuation">,</span> <span class="token string">'2'</span><span class="token punctuation">,</span> <span class="token string">'3'</span><span class="token punctuation">]</span>
<span class="token comment">// 检查传入的projectId是否有效</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>validProjects<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>params<span class="token punctuation">.</span>projectId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">notFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 如果无效,触发404页面</span>
<span class="token punctuation">}</span>
<span class="token comment">// 返回项目详情页面</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>div<span class="token operator">&gt;</span> <span class="token comment">// 容器div</span>
<span class="token operator">&lt;</span>h1<span class="token operator">&gt;</span>Project Detail<span class="token operator">:</span> <span class="token punctuation">{</span>params<span class="token punctuation">.</span>projectId<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">&gt;</span> <span class="token comment">// 显示项目ID</span>
    <span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/projects"</span><span class="token operator">&gt;</span>Back to Projects<span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span> <span class="token comment">// 返回项目列表的链接</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">}</span></code></pre>
<hr><h3>5. 设置页权限控制 (<code>app/settings/layout.tsx</code>)</h3><blockquote><p><strong>关键点</strong>:权限检查放在布局层(避免页面加载后再重定向)</p></blockquote>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/settings/layout.tsx</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> cookies <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/headers'</span> <span class="token comment">// 用于操作HTTP cookies</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> redirect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/navigation'</span> <span class="token comment">// 用于重定向页面</span>
<span class="token comment">// 设置页布局组件,用于权限控制</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">SettingsLayout</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
children<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span>
children<span class="token operator">:</span> React<span class="token punctuation">.</span>ReactNode <span class="token comment">// 代表子组件(设置页面内容)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 从cookies中获取token(用于用户认证)</span>
<span class="token keyword">const</span> token <span class="token operator">=</span> <span class="token function">cookies</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'token'</span><span class="token punctuation">)</span><span class="token operator">?.</span>value
<span class="token comment">// 如果没有token(用户未登录),重定向到登录页</span>
<span class="token comment">// encodeURIComponent确保URL安全</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>token<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">redirect</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/signin?next=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span><span class="token string">'/settings'</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// 如果有token,渲染设置页面内容</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>h2<span class="token operator">&gt;</span>Settings<span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">&gt;</span> <span class="token comment">// 设置页面标题</span>
    <span class="token punctuation">{</span>children<span class="token punctuation">}</span> <span class="token comment">// 渲染子组件(实际的设置内容)</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">}</span></code></pre>
<hr><h3>6. 登录页 (<code>app/signin/page.tsx</code>)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/signin/page.tsx</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> cookies <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/headers'</span> <span class="token comment">// 用于操作HTTP cookies</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> redirect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/navigation'</span> <span class="token comment">// 用于重定向页面</span>
<span class="token comment">// 登录页面组件</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">SignIn</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 模拟登录逻辑的函数</span>
<span class="token keyword">const</span> <span class="token function-variable function">handleLogin</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token comment">// 设置cookie(存储token)</span>
<span class="token function">cookies</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token string">'token'</span><span class="token punctuation">,</span> <span class="token string">'dummy_token'</span><span class="token punctuation">)</span>
<span class="token comment">// 从cookies中获取重定向路径(如果之前访问的是受保护页面)</span>
<span class="token keyword">const</span> next <span class="token operator">=</span> <span class="token function">cookies</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'next'</span><span class="token punctuation">)</span><span class="token operator">?.</span>value <span class="token operator">||</span> <span class="token string">'/'</span>
<span class="token comment">// 重定向到目标页面</span>
<span class="token function">redirect</span><span class="token punctuation">(</span>next<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>h1<span class="token operator">&gt;</span>Login<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">&gt;</span> <span class="token comment">// 登录页面标题</span>
    <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span>handleLogin<span class="token punctuation">}</span><span class="token operator">&gt;</span>Sign In<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">&gt;</span> <span class="token comment">// 登录按钮</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">}</span></code></pre>
<hr><h3>7. 404 页面 (<code>app/not-found.tsx</code>)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/not-found.tsx</span>
<span class="token comment">// 404页面组件(当请求的页面不存在时显示)</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>main<span class="token operator">&gt;</span> <span class="token comment">// 主要内容区域</span>
<span class="token operator">&lt;</span>h1<span class="token operator">&gt;</span><span class="token number">404</span> <span class="token operator">-</span> Page Not Found<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">&gt;</span> <span class="token comment">// 404标题</span>
    <span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/"</span><span class="token operator">&gt;</span>Go Home<span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span> <span class="token comment">// 返回首页的链接</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>main<span class="token operator">&gt;</span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">}</span></code></pre>
<hr><h3>8. 错误处理 (<code>app/error.tsx</code>)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-ts"><span class="token comment">// app/error.tsx</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> notFound <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/navigation'</span> <span class="token comment">// 用于触发404页面的函数</span>
<span class="token comment">// 错误处理页面组件</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token operator">&lt;</span>main<span class="token operator">&gt;</span> <span class="token comment">// 主要内容区域</span>
<span class="token operator">&lt;</span>h1<span class="token operator">&gt;</span>Something went wrong<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">&gt;</span> <span class="token comment">// 错误提示</span>
    <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">notFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">&gt;</span>Go to <span class="token number">404</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">&gt;</span> <span class="token comment">// 点击按钮跳转到404页面</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>main<span class="token operator">&gt;</span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">}</span></code></pre>
<hr><h2> 与 React Router v6 的关键差异</h2><table><thead><tr><th>特性</th><th>React Router v6</th><th>Next.js (App Router)</th></tr></thead><tbody><tr><td><strong>路由定义</strong></td><td>显式配置文件 (<code>routes.tsx</code>)</td><td>文件系统自动映射 (<code>app/</code> 目录)</td></tr><tr><td><strong>布局</strong></td><td>通过 <code>children</code> 嵌套</td><td><code>layout.tsx</code> 文件 (自动继承)</td></tr><tr><td><strong>权限控制</strong></td><td>高阶组件 (<code>withAuth</code>)</td><td>在 <code>layout.tsx</code> 中直接重定向</td></tr><tr><td><strong>动态路由</strong></td><td><code>:param</code> 语法 (<code>/projects/:id</code>)</td><td><code></code> 语法 (<code>/projects/</code>)</td></tr><tr><td><strong>404 页面</strong></td><td><code>*</code> 通配符 + <code>errorElement</code></td><td><code>not-found.tsx</code> 文件</td></tr><tr><td><strong>懒加载</strong></td><td>手动 <code>lazy</code> + <code>Suspense</code></td><td><strong>自动按需加载</strong> (无需配置)</td></tr><tr><td><strong>重定向</strong></td><td><code>redirect</code> 函数 (路由配置中)</td><td><code>redirect</code> 函数 (在 <code>layout.tsx</code>/<code>page.tsx</code> 中)</td></tr></tbody></table><hr><h2>✅ 为什么 Next.js 的设计更优?</h2><h3>1. <strong>无需维护路由配置</strong>:文件系统即路由,新增页面只需创建文件</h3><h3>2. <strong>自动代码分割</strong>:按路由自动分割代码块(无需 <code>lazy</code>)</h3><h3>3. <strong>权限控制更合理</strong>:在布局层处理(避免页面加载后再重定向)</h3><h3>4. <strong>开发体验更好</strong>:文件命名即路由,直观易维护</h3><h3>5. <strong>内置 404/错误处理</strong>:<code>not-found.tsx</code> 和 <code>error.tsx</code> 自动生效</h3><h3>&gt;提示:Next.js 的权限控制<strong>不要</strong>在 <code>page.tsx</code> 中做(会先加载页面再重定向),必须放在 <code>layout.tsx</code> 中(如 <code>settings/layout.tsx</code>)。</h3><hr><h2> 运行效果</h2><table><thead><tr><th>URL</th><th>对应文件</th><th>效果</th></tr></thead><tbody><tr><td><code>/</code></td><td><code>app/page.tsx</code></td><td>首页</td></tr><tr><td><code>/projects</code></td><td><code>app/projects/page.tsx</code></td><td>项目列表</td></tr><tr><td><code>/projects/123</code></td><td><code>app/projects//page.tsx</code></td><td>项目详情 (动态参数)</td></tr><tr><td><code>/settings</code></td><td><code>app/settings/layout.tsx</code> + <code>page.tsx</code></td><td>受保护设置页 (需登录)</td></tr><tr><td><code>/signin</code></td><td><code>app/signin/page.tsx</code></td><td>登录页</td></tr><tr><td><code>/non-existent</code></td><td><code>app/not-found.tsx</code></td><td>自定义 404 页面</td></tr></tbody></table><hr><h2>&gt; ✨ <strong>关键总结</strong>:Next.js 的路由是<strong>声明式</strong>的(文件即路由),而 React Router 是<strong>配置式</strong>的(需要手动写路由表)。Next.js 的设计让路由与文件结构绑定,大幅降低维护成本,同时内置了最佳实践(权限、404、动态路由)。</h2></div><br><br>
来源:https://www.cnblogs.com/ycfenxi/p/19140866
頁: [1]
查看完整版本: Web典型路由结构之Next.js (App Router, v13+) )(文件系统驱动的路由:File-based Routing)声明式路由:记录即路由