Next.js 深入解析
<h1 id="nextjs-深入解析">Next.js 深入解析</h1><h2 id="一数据获取与渲染策略">一、数据获取与渲染策略</h2>
<h3 id="1-getstaticpaths---ssg-动态路径生成">1. <code>getStaticPaths</code> - SSG 动态路径生成</h3>
<p>用于生成静态页面的动态路由路径。</p>
<pre><code class="language-javascript">// 示例:pages/posts/.js
export async function getStaticPaths() {
return {
paths: [
// 1. 基础参数形式 pages/posts/.js → posts/1
{ params: { id: '1' } },
// 2. 嵌套动态路由 pages//.js → tech/nextjs-guide
{ params: { category: 'tech', slug: 'nextjs-guide' } },
// 3. 可选catch-all路由 pages/[...slug].js → docs/nextjs/api
{ params: { slug: ['docs', 'nextjs', 'api'] } },
],
// fallback 的三个选项(必选其一)
fallback: false, // 选项1: 只生成指定路径,其他路径返回404
fallback: true, // 选项2: 增量生成,显示加载状态降级方案
fallback: 'blocking' // 选项3: 增量生成,阻塞等待生成完成
};
}
</code></pre>
<p><strong>参数说明</strong>:</p>
<ul>
<li>输入参数一般为空,<code>context</code> 可能包含 <code>locales</code>(支持的语言列表)和 <code>defaultLocale</code>(默认语言),但在 Pages Router 中不常用。</li>
</ul>
<h3 id="2-getstaticprops---ssg-页面数据获取">2. <code>getStaticProps</code> - SSG 页面数据获取</h3>
<p>用于在构建时获取页面所需的数据。</p>
<pre><code class="language-javascript">export async function getStaticProps(context) {
// context.params 包含动态路由参数
const { params } = context;
return {
props: {
// 数据会被注入到页面组件
},
revalidate: 60, // ISR 关键:60秒后重新生成
notFound: false, // 如果为true,返回404页面
redirect: {
destination: '/',
permanent: false, // 临时重定向用307,永久重定向用308
}
};
}
</code></pre>
<p><strong>重要特性</strong>:</p>
<ul>
<li><code>props</code>、<code>notFound</code>、<code>redirect</code> 三选一返回</li>
<li><code>revalidate</code> 是 ISR(增量静态再生)的关键</li>
<li>SEO 友好:永久重定向用 <code>308</code>,临时用 <code>307</code></li>
<li>用户体验:<code>notFound</code> 比显示错误页更干净</li>
</ul>
<p><strong>参数来源</strong>:</p>
<ul>
<li><code>getStaticPaths</code> 返回的动态路由参数</li>
<li>全局注入的参数(国际化、预览模式等)</li>
</ul>
<h3 id="3-getserversideprops---ssr-实时数据获取">3. <code>getServerSideProps</code> - SSR 实时数据获取</h3>
<p>每次请求时获取最新数据。</p>
<pre><code class="language-javascript">export async function getServerSideProps({ req, res, params, query, locale }) {
// 参数说明:
// req → HTTP 请求对象(包含 cookie、header 等)
// res → HTTP 响应对象(可设置 header、cookie 等)
// params.id → 来自 URL 路由:/user/123
// query → 来自 URL 查询:/user/123?mode=compact
// locale → 来自 Accept-Language header 或 URL
return {
props: {
// 数据会被注入到页面组件
}
// 注意:没有 revalidate 参数
};
}
</code></pre>
<h2 id="二渲染策略对比">二、渲染策略对比</h2>
<table>
<thead>
<tr>
<th>策略</th>
<th>核心代码特征</th>
<th>使用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>SSG</td>
<td><code>getStaticProps()</code> 无 <code>revalidate</code></td>
<td>静态内容页</td>
</tr>
<tr>
<td>ISR</td>
<td><code>getStaticProps()</code> + <code>revalidate</code></td>
<td>频繁更新内容</td>
</tr>
<tr>
<td>SSR</td>
<td><code>getServerSideProps()</code></td>
<td>个性化/实时数据</td>
</tr>
<tr>
<td>CSR</td>
<td><code>useEffect()</code> / <code>useSWR()</code></td>
<td>用户交互数据</td>
</tr>
</tbody>
</table>
<p><strong>SSG和SSR方案都是在服务端就生成了完整html,所以需要客户端加载后进行水合,</strong><br>
<strong>而dynamic 的逻辑是React先渲染loading占位符,当动态组件的JS包下载完成后,React会重新渲染该位置为真实组件,没有水合(直接就是可交互),这一点更像是CSR的方案。</strong></p>
<h2 id="三动态导入与代码分割">三、动态导入与代码分割</h2>
<pre><code class="language-javascript">import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(
() => import('../components/Hello'),
{
ssr: true, // 默认值,表示服务端加载
loading: () => <p>Loading...</p>
}
);
</code></pre>
<p><strong>重要特性</strong>:</p>
<ul>
<li><code>ssr</code> 参数默认为 <code>true</code>,表示服务端加载</li>
<li>主要作用是代码分割:<code>dynamic</code> 包裹的组件会单独打包,按需加载</li>
<li>与常规 <code>import</code> 不同:常规 <code>import</code> 的组件会被包含在主包中</li>
<li>更像是 CSR 方案:React 先渲染 loading 占位符,JS 包下载完成后重新渲染为真实组件,没有水合过程</li>
</ul>
<p><strong>dynamic中ssr参数默认为true,当是默认值时,表示服务端加载,此时貌似和不使用dynamic没什么区别,</strong><br>
<strong>其主要作用是代码分割,正常import的组件会被默认包含在主包当中,而用dynamic包裹的import组件会单独打包,按需加载。</strong></p>
<h2 id="四优化功能">四、优化功能</h2>
<h3 id="1-image-优化">1. Image 优化</h3>
<ul>
<li>自动图片优化(尺寸、格式、懒加载)</li>
<li>支持 WebP 等现代格式</li>
<li>防止布局偏移</li>
</ul>
<h3 id="2-font-优化">2. Font 优化</h3>
<ul>
<li>自动字体优化和预加载</li>
<li>减少布局偏移</li>
</ul>
<h3 id="3-script-优化">3. Script 优化</h3>
<ul>
<li>优化第三方脚本加载</li>
<li>支持 <code>strategy</code> 属性(<code>beforeInteractive</code>、<code>afterInteractive</code>、<code>lazyOnload</code>)</li>
</ul>
<h2 id="五路由系统">五、路由系统</h2>
<h3 id="1-页面路由pages-router">1. 页面路由(Pages Router)</h3>
<ul>
<li>基于文件系统的路由</li>
<li>支持动态路由:<code>.js</code>、<code>[...slug].js</code></li>
<li>支持嵌套路由</li>
</ul>
<h3 id="2-浅路由shallow-routing">2. 浅路由(Shallow Routing)</h3>
<pre><code class="language-javascript">const router = useRouter();
router.push(
'/products?page=2',
undefined,
{ shallow: true }// 关键参数
);
</code></pre>
<p><strong>使用场景</strong>:筛选排序、分页、标签页切换等</p>
<ul>
<li>普通路由跳转:改变 URL → 重新获取数据 → 重新渲染组件</li>
<li>浅路由跳转:改变 URL → 直接更新 URL → 保持当前组件状态</li>
</ul>
<p><strong>浅路由最适合那些只需要更新查询参数的场景,而页面内容可以通过客户端逻辑更新</strong><br>
<strong>目前主流使用app router已不常用</strong></p>
<h3 id="3-api-路由">3. API 路由</h3>
<ul>
<li>在 <code>pages/api/</code> 目录下的文件可作为后端接口</li>
<li>接收 <code>req</code> 和 <code>res</code> 参数</li>
<li>让 Next.js 成为全栈框架</li>
</ul>
<p><strong>API 路由让 Next.js 从一个前端框架变成了全栈框架,是 Next.js 最重要的特性之一,</strong><br>
<strong>简单来说就是一个page下的api文件夹中的文件,都可以作为后端的接口直接访问,参数是req和res。</strong></p>
<h3 id="4-中间件">4. 中间件</h3>
<ul>
<li>旧版本:在 <code>pages/</code> 目录创建 <code>_middleware.js</code> 文件</li>
<li>新版本推荐:在项目根目录创建 <code>middleware.js</code> 文件</li>
</ul>
<h2 id="六app-router现代方案">六、App Router(现代方案)</h2>
<p>更现代化的方案,更推荐使用。核心性能优势是按需水合。有use client声明就水合,没有就不触发水合。</p>
<h3 id="1-数据获取简化">1. 数据获取简化</h3>
<ul>
<li><code>getStaticProps</code> → 直接使用 <code>async</code> 组件函数(默认 SSG,会缓存数据)</li>
<li><code>getStaticPaths</code> → <code>generateStaticParams</code><pre><code class="language-javascript">export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }];
}
export const dynamicParams = true; // 相当于 fallback: true
</code></pre>
</li>
<li><code>getServerSideProps</code> → 使用以下方式之一:<pre><code class="language-javascript">import { unstable_noStore } from 'next/cache';
export const dynamic = 'force-dynamic'; // 强制动态渲染
// 或使用 cache: 'no-store'
// 或使用动态函数(如 cookies(), headers())
</code></pre>
</li>
</ul>
<h3 id="2-metadata-api">2. Metadata API</h3>
<ul>
<li>用于更好的 SEO</li>
<li><code>generateMetadata</code> 函数可生成动态元数据</li>
</ul>
<h3 id="3-isr-实现">3. ISR 实现</h3>
<pre><code class="language-javascript">export const revalidate = 60; // 每60秒重新验证
// 或
fetch(url, { next: { revalidate: 60 } });
</code></pre>
<h3 id="4-客户端组件">4. 客户端组件</h3>
<ul>
<li>需要在文件头部显式添加 <code>'use client'</code> 指令</li>
<li>默认部分水合:服务器组件不水合,客户端组件水合</li>
</ul>
<h3 id="5-动态导入优化">5. 动态导入优化</h3>
<pre><code class="language-javascript">dynamic(..., { ssr: false }) // 主要用来延迟加载
</code></pre>
<p><strong>水合条件</strong>:</p>
<ol>
<li>组件标记为 <code>'use client'</code>(需要交互)</li>
<li>组件在服务端渲染了 HTML(<code>ssr: true</code> 或默认)</li>
</ol>
<p><strong>无水合情况</strong>:</p>
<ol>
<li>服务器组件(无 <code>'use client'</code>)</li>
<li>客户端组件但 <code>ssr: false</code></li>
<li>纯 CSR 组件(无服务端渲染)</li>
</ol>
<h3 id="6-流式渲染">6. 流式渲染</h3>
<ul>
<li>App Router 自动支持流式渲染</li>
<li>需要使用 <code><Suspense></code> 包裹异步内容</li>
<li>每个 <code><Suspense></code> 边界独立流式传输</li>
</ul>
<h3 id="7-server-actions">7. Server Actions</h3>
<ul>
<li>使用 <code>'use server'</code> 声明的函数</li>
<li>底层是网络通信,但封装为类似本地函数调用的体验</li>
<li>简化表单处理,无需创建复杂 API 路由</li>
</ul>
<p><strong>Server Actions的底层是一次网络通信,但Next.js把它封装成了看似“本地函数调用”的体验。</strong><br>
<strong>所以才可以直接使用 'use server'声明之后,其他组件直接调用这个函数就可以了。</strong><br>
<strong>既然nextjs中前端代码和后端逻辑在同一个项目中,所以对于简单的表单,省去了创建复杂api的逻辑,直接调函数。</strong></p>
<h3 id="8-其他特性">8. 其他特性</h3>
<ul>
<li><strong>中间件</strong>:在项目根目录的 <code>middleware.js</code> 文件中定义,用于身份验证、国际化、重定向等</li>
<li><strong>路由组</strong>:用 <code>( )</code> 包裹文件夹名,用于组织文件夹结构</li>
<li><strong>Cookie/Headers</strong>:在服务端组件中通过 <code>cookies()</code>、<code>headers()</code> 函数获取请求信息</li>
<li><strong>拦截路由</strong>:通过 <code>(.)</code> 前缀实现同级目录拦截,常用于模态框</li>
<li><strong>客户端导航</strong>:
<ul>
<li><code>usePathname</code>:获取当前路径(根路径是 <code>"/"</code>)</li>
<li><code>useSearchParams</code>:获取查询参数</li>
<li><code>useRouter</code>:切换路由</li>
<li><strong>注意</strong>:必须在客户端组件(<code>'use client'</code>)中使用,且 <code>usePathname</code> 和 <code>useSearchParams</code> 会导致动态渲染</li>
</ul>
</li>
</ul>
<h2 id="七总结">七、总结</h2>
<h3 id="开发建议">开发建议</h3>
<ol>
<li><strong>需要交互的组件</strong>:使用 <code>'use client'</code></li>
<li><strong>需要延迟加载的组件</strong>:使用 <code>dynamic</code></li>
<li><strong>需要定期更新的组件</strong>:使用 <code>revalidate</code></li>
<li><strong>简化数据获取</strong>:考虑使用 <code>useSWR</code> 进行状态管理</li>
</ol>
<h3 id="策略选择">策略选择</h3>
<ul>
<li><strong>简单静态页面</strong>:使用 App Router 的默认 SSG</li>
<li><strong>需要实时数据</strong>:使用 <code>dynamic = 'force-dynamic'</code> 或 <code>unstable_noStore()</code></li>
<li><strong>频繁更新内容</strong>:使用 ISR(<code>revalidate</code>)</li>
<li><strong>用户交互数据</strong>:使用 CSR(<code>useEffect</code>/<code>useSWR</code>)</li>
</ul><br><br>
来源:https://www.cnblogs.com/ggonekim/p/19405248
頁:
[1]