-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path1300265934.html
More file actions
1680 lines (1287 loc) · 130 KB
/
Copy path1300265934.html
File metadata and controls
1680 lines (1287 loc) · 130 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en" data-default-color-scheme="auto">
<head>
<meta charset="UTF-8">
<link rel="apple-touch-icon" sizes="76x76" href="/img/fluid.png">
<link rel="icon" href="/img/fluid.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="theme-color" content="#2f4154">
<meta name="author" content="FangZhou">
<meta name="keywords" content="nodejs,nextjs,react">
<meta name="description" content="Next.js框架学习笔记,涵盖服务端渲染、路由系统和App Router等核心概念">
<meta property="og:type" content="article">
<meta property="og:title" content="Nextjs学习笔记">
<meta property="og:url" content="http://arkpln.github.io/1300265934.html">
<meta property="og:site_name" content="Ark的技术博客">
<meta property="og:description" content="Next.js框架学习笔记,涵盖服务端渲染、路由系统和App Router等核心概念">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="http://arkpln.github.io/images/nextjs.webp">
<meta property="article:published_time" content="2025-12-24T11:02:55.000Z">
<meta property="article:modified_time" content="2026-04-24T02:47:10.577Z">
<meta property="article:author" content="FangZhou">
<meta property="article:tag" content="react">
<meta property="article:tag" content="nextjs">
<meta property="article:tag" content="ssr">
<meta property="article:tag" content="server-side-rendering">
<meta property="article:tag" content="app-router">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="http://arkpln.github.io/images/nextjs.webp">
<title>Nextjs学习笔记 - Ark的技术博客</title>
<link rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css">
<link rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css">
<link rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css">
<!-- 主题依赖的图标库,不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_1749284_5i9bdhy70f8.css">
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_1736178_k526ubmyhba.css">
<link rel="stylesheet" href="/css/main.css">
<link id="highlight-css" rel="stylesheet" href="/css/highlight.css">
<link id="highlight-css-dark" rel="stylesheet" href="/css/highlight-dark.css">
<script id="fluid-configs">
var Fluid = window.Fluid || {};
Fluid.ctx = Object.assign({}, Fluid.ctx)
var CONFIG = {"hostname":"arkpln.github.io","root":"/","version":"1.9.8","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"follow_dnt":true,"baidu":null,"google":{"measurement_id":null},"tencent":{"sid":null,"cid":null},"leancloud":{"app_id":"mEwkD7geCX7RWNO8srhDE2oP-gzGzoHsz","app_key":"4EF76hyiVA1QILHZThVbfoat","server_url":"https://mewkd7ge.lc-cn-n1-shared.com","path":"window.location.pathname","ignore_local":false},"umami":{"src":null,"website_id":null,"domains":null,"start_time":"2024-01-01T00:00:00.000Z","token":null,"api_server":null}},"search_path":"/local-search.xml","include_content_in_search":true};
if (CONFIG.web_analytics.follow_dnt) {
var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
}
</script>
<script src="/js/utils.js"></script>
<script src="/js/color-schema.js"></script>
<meta name="google-site-verification" content="5KfTZvtlydmBIzY97rVo-caP5jljAfmD3Qs3sBzY2Go"><style>ins.adsbygoogle[data-ad-status="unfilled"] { display: none !important; }</style>
<meta name="generator" content="Hexo 7.3.0"></head>
<body>
<header>
<div class="header-inner" style="height: 70vh;">
<nav id="navbar" class="navbar fixed-top navbar-expand-lg navbar-dark scrolling-navbar">
<div class="container">
<a class="navbar-brand" href="/">
<strong>Ark的技术博客</strong>
</a>
<button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<div class="animated-icon"><span></span><span></span><span></span></div>
</button>
<!-- Collapsible content -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto text-center">
<li class="nav-item">
<a class="nav-link" href="/" target="_self">
<i class="iconfont icon-home-fill"></i>
<span>快乐老家</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/archives/" target="_self">
<i class="iconfont icon-archive-fill"></i>
<span>笔岚档案</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/categories/" target="_self">
<i class="iconfont icon-category-fill"></i>
<span>文以类聚</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/tags/" target="_self">
<i class="iconfont icon-tags-fill"></i>
<span>按标签阅览</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about/" target="_self">
<i class="iconfont icon-user-fill"></i>
<span>联系我</span>
</a>
</li>
<li class="nav-item" id="search-btn">
<a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
<i class="iconfont icon-search"></i>
</a>
</li>
<li class="nav-item" id="color-toggle-btn">
<a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">
<i class="iconfont icon-dark" id="color-toggle-icon"></i>
</a>
</li>
</ul>
</div>
</div>
</nav>
<div id="banner" class="banner" parallax="true" style="background: url('/images/nextjs.webp') no-repeat center center; background-size: cover;">
<div class="full-bg-img">
<div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
<div class="banner-text text-center fade-in-up">
<div class="h2">
<span id="subtitle" data-typed-text="Nextjs学习笔记"></span>
</div>
<div class="mt-3">
<span class="post-meta">
<i class="iconfont icon-date-fill" aria-hidden="true"></i>
<time datetime="2025-12-24 19:02" pubdate>
December 24, 2025 pm
</time>
</span>
</div>
<div class="mt-1">
<span class="post-meta mr-2">
<i class="iconfont icon-chart"></i>
25k words
</span>
<span class="post-meta mr-2">
<i class="iconfont icon-clock-fill"></i>
205 mins
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</header>
<main>
<div class="container-fluid nopadding-x">
<div class="row nomargin-x">
<div class="side-col d-none d-lg-block col-lg-2">
<aside class="sidebar d-none d-xl-block" style="margin-right:-1rem;z-index:-1"><ins class="adsbygoogle" style="display:flex;justify-content:center;min-width:160px;max-width:300px;width:100%;height:600px;position:sticky;top:2rem" data-ad-client="ca-pub-3240358922862141" data-ad-slot="6522434837"></ins><script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></aside>
</div>
<div class="col-lg-8 nopadding-x-md">
<div class="container nopadding-x-md" id="board-ctn">
<div id="board">
<article class="post-content mx-auto">
<h1 id="seo-header">Nextjs学习笔记</h1>
<div class="markdown-body">
<h1 id="Next-js学习笔记"><a href="#Next-js学习笔记" class="headerlink" title="Next.js学习笔记"></a>Next.js学习笔记</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><h3 id="为什么选择Next-js"><a href="#为什么选择Next-js" class="headerlink" title="为什么选择Next.js"></a>为什么选择Next.js</h3><ol>
<li><p>用的人多</p>
</li>
<li><p>SSR服务端渲染技术</p>
</li>
<li><p>SEO搜索引擎优化</p>
</li>
<li><p>AI产品研发[AI-SDK]</p>
</li>
</ol>
<h3 id="前置知识条件"><a href="#前置知识条件" class="headerlink" title="前置知识条件:"></a>前置知识条件:</h3><ol>
<li><p>HTML,CSS,JavaScript</p>
</li>
<li><p>TypeScript</p>
</li>
<li><p>React</p>
</li>
<li><p>Node.js</p>
</li>
</ol>
<p><strong>视频链接:</strong> <a target="_blank" rel="external nofollow noopener noreferrer" href="https://www.bilibili.com/video/BV1P9CxBsEUA">Next.js(16) + AI</a></p>
<h2 id="搭建项目"><a href="#搭建项目" class="headerlink" title="搭建项目"></a>搭建项目</h2><h3 id="脚手架安装"><a href="#脚手架安装" class="headerlink" title="脚手架安装"></a>脚手架安装</h3><p>使用npm脚手架搭建</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npx create-next-app@latest <span class="hljs-comment">#npm 包管理器</span><br></code></pre></td></tr></table></figure>
<p>或者:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pnpm dlx create-next-app@latest <span class="hljs-comment">#pnpm包管理器</span><br></code></pre></td></tr></table></figure>
<h3 id="关键配置项"><a href="#关键配置项" class="headerlink" title="关键配置项"></a>关键配置项</h3><ol>
<li><p><code>TypeScript</code>:现代前端项目工程化语言,用于取代JavaScript提供更好的类型检查</p>
</li>
<li><p><code>Linter</code>:根据方案识别并报告 ECMAScript/JavaScript 代码问题的工具,其目的是使代码风格更加一致并避免错误。</p>
</li>
<li><p><code>React Compiler</code>:自动完成React应用中组件和Hooks的缓存化,于25年10月7日发布首个稳定版</p>
</li>
<li><p><code>TailwindCSS</code>:CSS原子化</p>
</li>
<li><p><code>App Router</code>:项目的路由系统</p>
</li>
</ol>
<h3 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h3><ul>
<li><p><code>node_modules</code>:npm依赖</p>
</li>
<li><p><code>src/app</code>:应用代码</p>
<ul>
<li><p><code>layout.tsx</code>:根布局组件</p>
</li>
<li><p><code>page.tsx</code>:主页,应用页面</p>
</li>
</ul>
</li>
<li><p><code>next.config.ts</code>:项目配置文件</p>
</li>
<li><p><code>package.json</code>:项目依赖配置</p>
</li>
<li><p><code>postcss.config.mjs</code>:PostCSS配置</p>
</li>
<li><p><code>tsconfig.json</code>:TypeScript配置文件</p>
</li>
</ul>
<h3 id="启动项目"><a href="#启动项目" class="headerlink" title="启动项目"></a>启动项目</h3><ul>
<li><strong>启动</strong></li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pnpm run dev<br></code></pre></td></tr></table></figure>
<ul>
<li><strong>编译生成环境</strong></li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pnpm run build<br></code></pre></td></tr></table></figure>
<h3 id="OneMoreThings"><a href="#OneMoreThings" class="headerlink" title="OneMoreThings"></a>OneMoreThings</h3><ol>
<li>什么是Turbopack?</li>
</ol>
<p>使用Rust编写的打包工具,性能是webpack的700倍</p>
<p>好处?</p>
<ol>
<li><p>统一环境,统一依赖图关系</p>
</li>
<li><p>惰性打包,只打包开发服务器请求的内容,减少首次启动时间</p>
</li>
<li><p>增量计算,多线程计算+函数级缓存,一旦完成就不再重新重复计算直到函数被更新</p>
</li>
<li><p>什么是React Compiler?</p>
</li>
</ol>
<p>自动添加<code>useMemo</code>,<code>useCallback</code>,<code>React.memo</code>,减少心智负担.</p>
<p>可通过添加<code>babel-plugin-react-compiler</code>依赖,在nextConfig中设置开启.</p>
<ol start="3">
<li>什么是App Router?什么是Page Router?</li>
</ol>
<ul>
<li>都是基于文件系统的路由</li>
</ul>
<p>在传统的框架中,路由需要通过路由表手动配置,而Next.js使用了自动的配置方式</p>
<ul>
<li><p><code>Page Router</code>: 将<code>pages</code>目录下所有的<code>jsx/tsx</code>文件都转换成路由,如<code>pages/index.tsx</code>会转换成<code>/</code>.<strong>问题:</strong> 不能把任何组件写到<code>pages</code>目录下,需要通过<code>_Component</code>的方式才能排除路由</p>
</li>
<li><p><code>App Router</code>: 根据约束定义:</p>
<ul>
<li><p><code>src/app/page.tsx</code>: / 首页</p>
</li>
<li><p><code>layout.tsx</code>: 布局组件</p>
</li>
<li><p><code>template.tsx</code>: 模版组件</p>
</li>
<li><p><code>loading.tsx</code>:加载组件</p>
</li>
<li><p><code>error.tsx</code>:错误组件</p>
</li>
<li><p><code>not-found.tsx</code>:404组件</p>
</li>
<li><p><code>/subpage/page.tsx</code>: <code>/subpage</code>子路由页面</p>
</li>
</ul>
</li>
<li><p><code>Page Router</code>读取数据使用<code>getServerSideProps</code>,<code>getStaticProps</code>,<code>getStaticPaths</code>等函数,而<code>App Router</code>则不需要,使用<code>fetch</code>即可</p>
</li>
</ul>
<h2 id="路由导航"><a href="#路由导航" class="headerlink" title="路由导航"></a>路由导航</h2><p>路由导航指的是在Nextjs中实现页面跳转的方式,在Next.js中共有四种方式提供跳转.</p>
<ul>
<li><p><code>Link</code>组件</p>
</li>
<li><p><code>useRouter</code>Hooks函数(客户端组件)</p>
</li>
<li><p><code>redirect</code>函数(服务端组件)</p>
</li>
<li><p><code>History API</code> Web API标准</p>
</li>
</ul>
<h3 id="Link组件"><a href="#Link组件" class="headerlink" title="Link组件"></a>Link组件</h3><p><code><Link></code>是一个内置组件,在a标签的基础上拓展了<code>prefetch</code>,<code>scroll</code>等功能</p>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">About</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black"</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex min-h-screen w-full max-w-3xl flex-col items-center justify-center py-32 px-16 bg-white dark:bg-black sm:items-start"</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50"</span>></span></span><br><span class="language-xml"> This is the About Page.</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">h1</span>></span></span><br><span class="language-xml"></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/'</span></span></span><br><span class="hljs-tag"><span class="language-xml"> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"</span>></span></span><br><span class="language-xml"> Home</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">Link</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">main</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span><br> );<br>}<br></code></pre></td></tr></table></figure>
<h4 id="携带查询参数"><a href="#携带查询参数" class="headerlink" title="携带查询参数"></a>携带查询参数</h4><p>写法: <code>href={{pathname:'/about/me',query:{id:1}}}</code></p>
<p>获取查询参数:<code>useSearchParams()</code></p>
<p>使用<code>searchParams.get()</code>/<code>searchParams.getAll()</code>获取查询参数</p>
<p>使用<code>...has()</code>判断查询字段是否存在</p>
<p><em><code>useSerachParams</code>是客户端函数,只能在客户端组件生效</em></p>
<p><strong>注意:</strong> 使用<code>useSearchParam</code>的组件需要包裹在<code><Suspense></Suspense></code>的组件内,在Next.js中,推荐在<code>layout.tsx</code>配置相关布局。</p>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-string">'use client'</span>;<br><span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;<br><span class="hljs-keyword">import</span> {useSearchParams} <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>;<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">About</span>(<span class="hljs-params"></span>) {<br><br> <span class="hljs-keyword">const</span> searchParams = <span class="hljs-title function_">useSearchParams</span>();<br> <span class="hljs-keyword">const</span> id = searchParams.<span class="hljs-title function_">get</span>(<span class="hljs-string">'id'</span>); <span class="hljs-comment">//返回字符串 </span><br> <span class="hljs-keyword">const</span> ids = searchParams.<span class="hljs-title function_">getAll</span>(<span class="hljs-string">'id'</span>); <span class="hljs-comment">//返回数组</span><br> <span class="hljs-keyword">const</span> isId = searchParams.<span class="hljs-title function_">has</span>(<span class="hljs-string">'id'</span>) <span class="hljs-comment">//返回布尔值</span><br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Query id:'</span>, id);<br><br><br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black"</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex min-h-screen w-full max-w-3xl flex-col items-center justify-center py-32 px-16 bg-white dark:bg-black sm:items-start"</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50"</span>></span></span><br><span class="language-xml"> This is the About Page.</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">h1</span>></span></span><br><span class="language-xml"></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/'</span></span></span><br><span class="hljs-tag"><span class="language-xml"> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"</span>></span></span><br><span class="language-xml"> Home</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">Link</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">main</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span><br> );<br>}<br></code></pre></td></tr></table></figure>
<h4 id="prefetch预加载"><a href="#prefetch预加载" class="headerlink" title="prefetch预加载"></a>prefetch预加载</h4><p>语法:<code>prefetch:{true}</code></p>
<p>启用后会在浏览器访问到当前组件后预加载Link指向的页面内容</p>
<h4 id="scroll保持滚动位置"><a href="#scroll保持滚动位置" class="headerlink" title="scroll保持滚动位置"></a>scroll保持滚动位置</h4><p>语法:<code>scroll:{true}</code> <strong>默认开启</strong></p>
<p>启用后可以保持跳转到的新页面始终为最顶端开始滚动</p>
<h4 id="replace舍弃历史记录"><a href="#replace舍弃历史记录" class="headerlink" title="replace舍弃历史记录"></a>replace舍弃历史记录</h4><p>语法: <code>replace</code></p>
<p>启用后舍弃先前的历史跳转记录</p>
<h3 id="useRouter-Hooks"><a href="#useRouter-Hooks" class="headerlink" title="useRouter Hooks"></a>useRouter Hooks</h3><p><code>useRouter</code>可以在代码中根据逻辑跳转页面,但它必须使用在客户端组件中,须声明为<code>'use client'</code></p>
<ul>
<li><p><strong><code>Page Router</code></strong> -> <strong><code>next/router</code></strong></p>
</li>
<li><p><strong><code>App Router</code></strong> -> <strong><code>next/navigation</code></strong></p>
</li>
</ul>
<h4 id="router-push"><a href="#router-push" class="headerlink" title="router.push"></a>router.push</h4><p><code>router.push(href: string, { scroll: boolean })</code> : 执行客户端导航至提供的路由。会在浏览器的历史记录栈中添加一个新条目。</p>
<h4 id="router-prefetch"><a href="#router-prefetch" class="headerlink" title="router.prefetch"></a>router.prefetch</h4><p><code>router.prefetch(href: string, options?: { onInvalidate?: () => void })</code> : <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/getting-started/linking-and-navigating#prefetching">预获取</a>提供的路由,以实现更快的客户端切换。当<a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/guides/prefetching#extending-or-ejecting-link">预获取的数据过期</a>时,可选的 <code>onInvalidate</code> 回调函数将被调用。</p>
<h4 id="router-refresh"><a href="#router-refresh" class="headerlink" title="router.refresh"></a>router.refresh</h4><p><code>router.refresh()</code>:刷新当前路由。向服务器发起新的请求,重新获取数据并重新渲染服务器组件。客户端将合并更新后的 React 服务器组件数据,而不会丢失未受影响的客户端 React 状态(例如 <code>useState</code>)或浏览器状态(例如滚动位置)。</p>
<h4 id="router-back"><a href="#router-back" class="headerlink" title="router.back"></a>router.back</h4><p><code>router.back()</code>:导航回浏览器历史记录中的上一个路由。</p>
<h4 id="router-forward"><a href="#router-forward" class="headerlink" title="router.forward"></a>router.forward</h4><p><code>router.forward()</code>:导航到浏览器历史记录中的下一个页面。</p>
<h4 id="router-replace"><a href="#router-replace" class="headerlink" title="router.replace"></a>router.replace</h4><p><code>router.replace(href: string, { scroll: boolean })</code> : 执行客户端导航至提供的路由,但不会在浏览器的历史记录栈中添加新条目。</p>
<h3 id="redirect函数"><a href="#redirect函数" class="headerlink" title="redirect函数"></a>redirect函数</h3><h4 id="redirect函数-1"><a href="#redirect函数-1" class="headerlink" title="redirect函数"></a><code>redirect</code>函数</h4><p><code>redirect</code> 函数允许你将用户重定向到另一个 URL。你可以在 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/getting-started/server-and-client-components">服务器组件</a> 、 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/api-reference/file-conventions/route">路由处理程序</a> 和 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/getting-started/updating-data">服务器操作</a> 中调用 <code>redirect</code>。</p>
<p><code>redirect</code> 通常在执行变更或事件后使用。例如,创建一篇文章时:</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-string">'use server'</span><br><br><span class="hljs-keyword">import</span> { redirect } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span><br><span class="hljs-keyword">import</span> { revalidatePath } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/cache'</span><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">createPost</span>(<span class="hljs-params"><span class="hljs-attr">id</span>: <span class="hljs-built_in">string</span></span>) {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">// Call database</span><br> } <span class="hljs-keyword">catch</span> (error) {<br> <span class="hljs-comment">// Handle errors</span><br> }<br><br> <span class="hljs-title function_">revalidatePath</span>(<span class="hljs-string">'/posts'</span>) <span class="hljs-comment">// Update cached posts</span><br> <span class="hljs-title function_">redirect</span>(<span class="hljs-string">`/post/<span class="hljs-subst">${id}</span>`</span>) <span class="hljs-comment">// Navigate to the new post page</span><br>}<br></code></pre></td></tr></table></figure>
<blockquote>
<p><strong>注意事项</strong> :</p>
<ul>
<li><code>redirect</code> 默认返回 307(临时重定向)状态码。在 Server Action 中使用时,返回 303(查看其他),这通常用于在 POST 请求后重定向到成功页面。</li>
<li><code>redirect</code> 会抛出错误,因此在使用 <code>try/catch</code> 语句时,应将其调用在 <code>try</code> 块 <strong>之外</strong> 。</li>
<li><code>redirect</code> 可以在客户端组件的渲染过程中调用,但不能在事件处理程序中调用。你可以改用 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/guides/redirecting#userouter-hook"><code>useRouter</code> 钩子</a> 。</li>
<li><code>redirect</code> 还接受绝对 URL,可用于重定向到外部链接。</li>
<li>如果希望在渲染过程之前进行重定向,请使用 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/guides/redirecting#redirects-in-nextconfigjs"><code>next.config.js</code></a> 或 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/guides/redirecting#nextresponseredirect-in-proxy">代理</a> 。</li>
</ul>
</blockquote>
<h4 id="permanentRedirect函数"><a href="#permanentRedirect函数" class="headerlink" title="permanentRedirect函数"></a><code>permanentRedirect</code>函数</h4><p><code>permanentRedirect</code> 函数可将用户<strong>永久</strong>重定向到另一个 URL。您可以在 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/getting-started/server-and-client-components">服务器组件</a> 、 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/api-reference/file-conventions/route">路由处理程序</a> 和 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/getting-started/updating-data">服务器操作</a> 中调用 <code>permanentRedirect</code>。</p>
<p><code>permanentRedirect</code> 通常在执行更改实体规范 URL 的操作或事件后使用,例如用户更改用户名后更新其个人资料 URL:</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-string">'use server'</span><br><br><span class="hljs-keyword">import</span> { permanentRedirect } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span><br><span class="hljs-keyword">import</span> { revalidateTag } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/cache'</span><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">updateUsername</span>(<span class="hljs-params"><span class="hljs-attr">username</span>: <span class="hljs-built_in">string</span>, <span class="hljs-attr">formData</span>: <span class="hljs-title class_">FormData</span></span>) {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">// Call database</span><br> } <span class="hljs-keyword">catch</span> (error) {<br> <span class="hljs-comment">// Handle errors</span><br> }<br><br> <span class="hljs-title function_">revalidateTag</span>(<span class="hljs-string">'username'</span>) <span class="hljs-comment">// Update all references to the username</span><br> <span class="hljs-title function_">permanentRedirect</span>(<span class="hljs-string">`/profile/<span class="hljs-subst">${username}</span>`</span>) <span class="hljs-comment">// Navigate to the new user profile</span><br>}<br></code></pre></td></tr></table></figure>
<blockquote>
<p><strong>注意事项</strong> :</p>
<ul>
<li><code>permanentRedirect</code> 默认返回 308(永久重定向)状态码。</li>
<li><code>permanentRedirect</code> 还接受绝对 URL,可用于重定向到外部链接。</li>
<li>如果希望在渲染过程之前进行重定向,请使用 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/guides/redirecting#redirects-in-nextconfigjs"><code>next.config.js</code></a> 或 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/guides/redirecting#nextresponseredirect-in-proxy">代理</a> 。</li>
</ul>
</blockquote>
<h2 id="动态路由"><a href="#动态路由" class="headerlink" title="动态路由"></a>动态路由</h2><h3 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h3><ol>
<li><p>创建[Params]文件夹,若要捕获多个参数则写作<code>[...Params]</code>,若表示可选参数可写作<code>[[...optional]]</code></p>
</li>
<li><p>在该文件夹下创建客户端组件<code>page.tsx</code></p>
</li>
<li><p>使用<code>useParams</code>Hooks获取参数,使用解耦或赋值的方式获取路径参数</p>
</li>
</ol>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><br><span class="hljs-comment">// src/app/about/[id]/page.tsx</span><br><span class="hljs-string">'use client'</span><br><br><span class="hljs-keyword">import</span> { useParams } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">AboutPage</span>(<span class="hljs-params"></span>) {<br><br> <span class="hljs-keyword">const</span> params = <span class="hljs-title function_">useParams</span>();<br> <span class="hljs-keyword">const</span> id = params.<span class="hljs-property">id</span>;<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Dynamic Route id:'</span>, id);<br><br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"h-500 flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black"</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex min-h-screen w-full max-w-3xl flex-col items-center justify-center py-32 px-16 bg-white dark:bg-black sm:items-start"</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50"</span>></span></span><br><span class="language-xml"> This is the About Page with ID: {id}</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">h1</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">main</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span><br><span class="language-xml"></span><br><span class="language-xml"></span><br><span class="language-xml"> <span class="hljs-tag"></></span></span><br><br> )<br><br><br>}<br></code></pre></td></tr></table></figure>
<h2 id="平行路由"><a href="#平行路由" class="headerlink" title="平行路由"></a>平行路由</h2><p>平行路由指的是在同一个布局中渲染多个页面,类似<code>Vue Router</code>的<code>Router-View</code></p>
<p>平行路由允许你在同一布局中同时或有条件地渲染一个或多个页面。它们适用于应用程序中高度动态的区域,例如仪表板和社交网站的动态信息流。</p>
<h3 id="基本方法"><a href="#基本方法" class="headerlink" title="基本方法"></a>基本方法</h3><ol>
<li><p>在<code>src/app</code>下创建<code>@slot</code>文件夹</p>
</li>
<li><p>在<code>@slot</code>文件夹下编写<code>page.tsx</code>和<code>default.js</code></p>
</li>
<li><p>在<code>layout.tsx</code>中定义和引入插槽。</p>
</li>
</ol>
<p><strong>注:</strong> </p>
<ul>
<li><p>在最新的<code>Next.js</code>中<strong>必须</strong>在<code>@slot</code>文件下创建<code>default.js</code>用于渲染失败。<a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/api-reference/file-conventions/default">File-system conventions: default.js | Next.js</a></p>
</li>
<li><p>在平行路由中,设置形如<code>loading.tsx</code>等友好提示页面是允许的。</p>
</li>
<li><p>在平行路由下,也可以继续配置路由导航。(软导航只会更新活跃内容,直接输入URL或刷新的硬导航会导致404)</p>
</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">//default.js</span><br><br><br><span class="hljs-keyword">import</span> { notFound } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Default</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-title function_">notFound</span>()<br>}<br></code></pre></td></tr></table></figure>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// ./src/app/layout.tsx</span><br><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">Metadata</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;<br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Geist</span>, <span class="hljs-title class_">Geist</span>_Mono } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/font/google"</span>;<br><span class="hljs-comment">// @ts-ignore</span><br><span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span><br><br><span class="hljs-keyword">const</span> geistSans = <span class="hljs-title class_">Geist</span>({<br> <span class="hljs-attr">variable</span>: <span class="hljs-string">"--font-geist-sans"</span>,<br> <span class="hljs-attr">subsets</span>: [<span class="hljs-string">"latin"</span>],<br>});<br><br><span class="hljs-keyword">const</span> geistMono = <span class="hljs-title class_">Geist</span>_Mono({<br> <span class="hljs-attr">variable</span>: <span class="hljs-string">"--font-geist-mono"</span>,<br> <span class="hljs-attr">subsets</span>: [<span class="hljs-string">"latin"</span>],<br>});<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">metadata</span>: <span class="hljs-title class_">Metadata</span> = {<br> <span class="hljs-attr">title</span>: <span class="hljs-string">"Create Next App"</span>,<br> <span class="hljs-attr">description</span>: <span class="hljs-string">"Generated by create next app"</span>,<br>};<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">RootLayout</span>(<span class="hljs-params">{</span><br><span class="hljs-params"> children,</span><br><span class="hljs-params"> home</span><br><span class="hljs-params">}: <span class="hljs-title class_">Readonly</span><{</span><br><span class="hljs-params"> children: React.ReactNode;</span><br><span class="hljs-params"> home: React.ReactNode;</span><br><span class="hljs-params"></span><br><span class="hljs-params">}></span>) {<br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">body</span></span></span><br><span class="hljs-tag"><span class="language-xml"> <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`${<span class="hljs-attr">geistSans.variable</span>} ${<span class="hljs-attr">geistMono.variable</span>} <span class="hljs-attr">antialiased</span>`}</span></span><br><span class="hljs-tag"><span class="language-xml"> ></span></span><br><span class="language-xml"> {children}</span><br><span class="language-xml"> {home}</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">body</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">html</span>></span></span><br> );<br>}<br><br><br></code></pre></td></tr></table></figure>
<h2 id="路由组"><a href="#路由组" class="headerlink" title="路由组"></a>路由组</h2><p>路由组是一种基于文件夹的约定范式,可以按类别或团队定义组织路由模块,并且不影响URL路径。</p>
<p><img src="https://nextjs.org/_next/image?url=https://h8DxKfmAPhn8O0p3.public.blob.vercel-storage.com/docs/light/project-organization-route-groups.png&w=3840&q=75" srcset="/img/loading.gif" lazyload></p>
<h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><ol>
<li><p>在<code>src/app</code>下创建<code>(routergroup)</code>文件夹</p>
</li>
<li><p>在<code>(routergroup)</code>文件夹根目录下创建<code>layout.tsx</code></p>
</li>
<li><p>在<code>(routergroup)</code>文件夹内创建相关文件夹路由和页面<code>page.tsx</code></p>
</li>
<li><p>在路由组文件夹根目录下的<code>layout.tsx</code>中插值子元素</p>
</li>
</ol>
<h2 id="路由处理程序"><a href="#路由处理程序" class="headerlink" title="路由处理程序"></a>路由处理程序</h2><p>Route Handlers 允许你使用 Web <a target="_blank" rel="external nofollow noopener noreferrer" href="https://developer.mozilla.org/docs/Web/API/Request">Request</a> 和 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://developer.mozilla.org/docs/Web/API/Response">Response</a> API 为指定路由创建自定义请求处理程序。</p>
<p><img src="https://nextjs.org/_next/image?url=https://h8DxKfmAPhn8O0p3.public.blob.vercel-storage.com/docs/light/route-special-file.png&w=3840&q=75" srcset="/img/loading.gif" lazyload></p>
<blockquote>
<p><strong>需要注意</strong> :Route Handlers 仅可在<code>App Router</code>中使用。它们相当于 <code>pages</code> 目录中的 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">API Routes</a>,不可同时的使用 API Routes 和 Route Handlers。</p>
</blockquote>
<h3 id="使用方法-1"><a href="#使用方法-1" class="headerlink" title="使用方法"></a>使用方法</h3><ol>
<li><p>在<code>src/app</code>根目录下创建<code>api</code>文件夹</p>
</li>
<li><p>在<code>api</code>文件夹内的子文件夹创建<code>route.ts</code></p>
</li>
<li><p>在<code>route.ts</code>中引入<code>NextRequest</code>和<code>NextResponse</code>方法,编写 <code>RESTful API</code> ,其函数名必须为<strong>全大写的HTTP请求方法</strong></p>
</li>
</ol>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// next-app/src/app/api/user/route.ts</span><br><br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">NextRequest</span>,<span class="hljs-title class_">NextResponse</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">GET</span>(<span class="hljs-params"><span class="hljs-attr">request</span>:<span class="hljs-title class_">NextRequest</span></span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>:<span class="hljs-string">'Hello, User!'</span>});<br><br>}<br></code></pre></td></tr></table></figure>
<figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-keyword">GET</span> <span class="hljs-string">http://localhost:3000/api/user</span> <span class="hljs-meta">HTTP/1.1</span><br><br><span class="language-http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">200</span> OK</span><br><span class="language-http"><span class="hljs-attribute">vary</span><span class="hljs-punctuation">: </span>rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch</span><br><span class="language-http"><span class="hljs-attribute">content-type</span><span class="hljs-punctuation">: </span>application/json</span><br><span class="language-http"><span class="hljs-attribute">Date</span><span class="hljs-punctuation">: </span>Fri, 02 Jan 2026 13:33:07 GMT</span><br><span class="language-http"><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>close</span><br><span class="language-http"><span class="hljs-attribute">Transfer-Encoding</span><span class="hljs-punctuation">: </span>chunked</span><br><span class="language-http"></span><br><span class="language-http"><span class="language-json"><span class="hljs-punctuation">{</span></span></span><br><span class="language-json"><span class="language-http"> <span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Hello, User!"</span></span></span><br><span class="language-json"><span class="language-http"><span class="hljs-punctuation">}</span></span></span><br><span class="language-json"><span class="language-http"></span></span><br></code></pre></td></tr></table></figure>
<h3 id="查询参数"><a href="#查询参数" class="headerlink" title="查询参数"></a>查询参数</h3><p>使用<code>const query = request.nextUrl.searchParams</code>获取查询对象,通过<code>query.get()</code>或<code>query.getAll()</code>方法获取字段值</p>
<h3 id="POST请求"><a href="#POST请求" class="headerlink" title="POST请求"></a>POST请求</h3><p>可使用标准<strong>Web API</strong> 方法,<code>formData()</code>方法读取请求体</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs ts"><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">POST</span>(<span class="hljs-params"><span class="hljs-attr">request</span>:<span class="hljs-title class_">NextRequest</span></span>) {<br><br> <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> request.<span class="hljs-title function_">json</span>();<br><br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Received data:'</span>, data);<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>:<span class="hljs-string">'User data received'</span>},{<span class="hljs-attr">status</span>:<span class="hljs-number">201</span>});<br><br>}<br></code></pre></td></tr></table></figure>
<h3 id="动态路由-1"><a href="#动态路由-1" class="headerlink" title="动态路由"></a>动态路由</h3><p>有时候网页项目中已使用了动态路由,此时须</p>
<ol>
<li><p>在相关路由文件夹下创建<code>[params]</code>的文件夹</p>
</li>
<li><p>在该文件夹内创建<code>route.ts</code>文件</p>
</li>
<li><p>通过<code>{params}</code>获取参数</p>
</li>
</ol>
<p><em>注:</em> 从Next.js v15开始 <code>context.params</code>字段<strong>是<code>Promise</code>对象</strong> ,须配合<code>await</code>获取参数。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// next-app/src/app/api/user/[id]/route.ts</span><br><br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">NextRequest</span>,<span class="hljs-title class_">NextResponse</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">GET</span>(<span class="hljs-params"><span class="hljs-attr">request</span>:<span class="hljs-title class_">NextRequest</span>,{params}: {params:<span class="hljs-built_in">Promise</span><{id:<span class="hljs-built_in">string</span>}>}</span>) {<br><br> <span class="hljs-keyword">const</span> {id} = <span class="hljs-keyword">await</span> params;<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'User ID:'</span>, id);<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>:<span class="hljs-string">`Hello, User with ID: <span class="hljs-subst">${id}</span>`</span>});<br><br>}<br></code></pre></td></tr></table></figure>
<h2 id="Cookies"><a href="#Cookies" class="headerlink" title="Cookies"></a>Cookies</h2><p><code>cookies</code> 是一个 <strong>异步</strong>函数,可在 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/getting-started/server-and-client-components">服务器组件</a> 中读取 HTTP 请求的 Cookie,也可在 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/getting-started/updating-data">服务器操作</a> 或 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/api-reference/file-conventions/route">路由处理程序</a> 中读取和写入出站请求的 Cookie。</p>
<h3 id="使用方法-2"><a href="#使用方法-2" class="headerlink" title="使用方法"></a>使用方法</h3><p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/api-reference/functions/cookies">功能: cookies | Next.js</a></p>
<p>以下方法可用:</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>返回类型</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td><code>get('name')</code></td>
<td>对象</td>
<td>接受一个 cookie 名称,并返回包含名称和值的对象。</td>
</tr>
<tr>
<td><code>getAll()</code></td>
<td>对象数组</td>
<td>返回所有名称匹配的 cookie 列表。</td>
</tr>
<tr>
<td><code>has('name')</code></td>
<td>布尔值</td>
<td>接受一个 cookie 名称,并根据该 cookie 是否存在返回一个布尔值。</td>
</tr>
<tr>
<td><code>set(name, value, options)</code></td>
<td>-</td>
<td>接受一个 cookie 名称、值和选项,并设置传出请求的 cookie。</td>
</tr>
<tr>
<td><code>delete(name)</code></td>
<td>-</td>
<td>接受一个 cookie 名称并删除该 cookie。</td>
</tr>
<tr>
<td><code>toString()</code></td>
<td>字符串</td>
<td>返回 cookies 的字符串表示形式。</td>
</tr>
</tbody></table>
<h3 id="选项"><a href="#选项" class="headerlink" title="选项"></a>选项</h3><p>设置 Cookie 时,以下 <code>options</code> 对象中的属性是受支持的:</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>类型</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td><code>name</code></td>
<td>字符串</td>
<td>指定 cookie 的名称。</td>
</tr>
<tr>
<td><code>value</code></td>
<td>字符串</td>
<td>指定要存储在 cookie 中的值。</td>
</tr>
<tr>
<td><code>expires</code></td>
<td>日期</td>
<td>定义 Cookie 的确切过期日期。</td>
</tr>
<tr>
<td><code>maxAge</code></td>
<td>数字</td>
<td>以秒为单位设置 Cookie 的生命周期。</td>
</tr>
<tr>
<td><code>domain</code></td>
<td>字符串</td>
<td>指定 Cookie 可用的域名。</td>
</tr>
<tr>
<td><code>path</code></td>
<td>字符串,默认值:<code>'/'</code></td>
<td>将 Cookie 的作用域限制在域名内的特定路径。</td>
</tr>
<tr>
<td><code>secure</code></td>
<td>布尔值</td>
<td>确保仅通过 HTTPS 连接发送 Cookie,以增强安全性。</td>
</tr>
<tr>
<td><code>httpOnly</code></td>
<td>布尔值</td>
<td>将 Cookie 限制为仅 HTTP 请求,防止客户端访问。</td>
</tr>
<tr>
<td><code>sameSite</code></td>
<td>布尔值,<code>'lax'</code>,<code>'strict'</code>,<code>'none'</code></td>
<td>控制 Cookie 的跨站点请求行为。</td>
</tr>
<tr>
<td><code>priority</code></td>
<td>字符串(<code>"low"</code>,<code>"medium"</code>,<code>"high"</code>)</td>
<td>指定 Cookie 的优先级</td>
</tr>
<tr>
<td><code>partitioned</code></td>
<td>布尔值</td>
<td>指示 Cookie 是否为 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/privacycg/CHIPS">分区</a> 。</td>
</tr>
</tbody></table>
<p>唯一具有默认值的选项是 <code>path</code>。</p>
<h3 id="示例-登录功能"><a href="#示例-登录功能" class="headerlink" title="示例(登录功能)"></a>示例(登录功能)</h3><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// next-app/src/app/api/login/route.ts</span><br><br><span class="hljs-keyword">import</span> {<span class="hljs-title class_">NextRequest</span>,<span class="hljs-title class_">NextResponse</span>} <span class="hljs-keyword">from</span> <span class="hljs-string">'next/server'</span>;<br><span class="hljs-keyword">import</span>{ cookies} <span class="hljs-keyword">from</span> <span class="hljs-string">'next/headers'</span>;<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">POST</span>(<span class="hljs-params"><span class="hljs-attr">request</span>: <span class="hljs-title class_">NextRequest</span></span>) {<br> <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">await</span> request.<span class="hljs-title function_">json</span>();<br> <span class="hljs-keyword">if</span>(body.<span class="hljs-property">username</span> ===<span class="hljs-string">'admin'</span>&& body.<span class="hljs-property">password</span> ===<span class="hljs-string">'123456'</span>){<br> <span class="hljs-keyword">const</span> cookieStore = <span class="hljs-keyword">await</span> <span class="hljs-title function_">cookies</span>();<br> cookieStore.<span class="hljs-title function_">set</span>(<span class="hljs-string">'token'</span>,<span class="hljs-string">'1234567890'</span>,{<br> <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">maxAge</span>: <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span> * <span class="hljs-number">30</span>, <span class="hljs-comment">// 30 day</span><br> });<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>:<span class="hljs-string">'Login successful'</span>,<span class="hljs-attr">code</span>:<span class="hljs-number">1</span>});<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>:<span class="hljs-string">'Invalid credentials'</span>,<span class="hljs-attr">code</span>:<span class="hljs-number">0</span>}, {<span class="hljs-attr">status</span>:<span class="hljs-number">401</span>});<br> }<br>}<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">GET</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">const</span> cookieStore = <span class="hljs-keyword">await</span> <span class="hljs-title function_">cookies</span>();<br> <span class="hljs-keyword">const</span> token = cookieStore.<span class="hljs-title function_">get</span>(<span class="hljs-string">'token'</span>);<br> <span class="hljs-keyword">if</span>(token && token.<span class="hljs-property">value</span> ===<span class="hljs-string">'1234567890'</span>){<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>:<span class="hljs-string">'User is authenticated'</span>,<span class="hljs-attr">code</span>:<span class="hljs-number">1</span>});<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>:<span class="hljs-string">'User is not authenticated'</span>,<span class="hljs-attr">code</span>:<span class="hljs-number">0</span>}, {<span class="hljs-attr">status</span>:<span class="hljs-number">401</span>});<br> }<br>}<br></code></pre></td></tr></table></figure>
<h2 id="AI-SDK"><a href="#AI-SDK" class="headerlink" title="AI-SDK"></a>AI-SDK</h2><p>Vercel开发的AI-SDK,用于构建AI产品和工具。</p>
<p>由于本篇内容与Next.js关系不大,故在此贴上链接。</p>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://ai-sdk.dev/docs/introduction">AI SDK by Vercel</a></p>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://www.bilibili.com/video/BV1P9CxBsEUA?p=9&vd_source=75123c5d6ddf7add04ab0e96d5ddb1f7&spm_id_from=333.788.videopod.episodes">Next.js(AI-SDK)_哔哩哔哩_bilibili</a></p>
<h2 id="Proxy"><a href="#Proxy" class="headerlink" title="Proxy"></a>Proxy</h2><blockquote>
<p>从 Next.js 16 开始,Middleware 现在被称为 Proxy,以更准确地反映其用途。功能保持不变。</p>
</blockquote>
<p>可通过命令一步升级</p>
<h3 id="使用方法-3"><a href="#使用方法-3" class="headerlink" title="使用方法"></a>使用方法</h3><ul>
<li><p>处理跨域请求</p>
</li>
<li><p>接口转发</p>
</li>
<li><p>配合第三方实现限流</p>
</li>
<li><p>鉴权</p>
</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// ./src/proxy.ts</span><br><span class="hljs-keyword">import</span> {<span class="hljs-title class_">NextRequest</span>, <span class="hljs-title class_">NextResponse</span>} <span class="hljs-keyword">from</span> <span class="hljs-string">'next/server'</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">proxy</span>(<span class="hljs-params"><span class="hljs-attr">request</span>: <span class="hljs-title class_">NextRequest</span></span>) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(request.<span class="hljs-property">url</span>)<br>}<br><br><span class="hljs-comment">// matcher: '/home/:path*'</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {<br> <span class="hljs-attr">matcher</span>: <span class="hljs-string">'/proxy/:path*'</span><br>};<br></code></pre></td></tr></table></figure>
<h3 id="请求器Matcher"><a href="#请求器Matcher" class="headerlink" title="请求器Matcher"></a>请求器Matcher</h3><ul>
<li><p><code>source</code>:匹配源路径</p>
</li>
<li><p><code>has</code>:匹配条件</p>
</li>
<li><p><code>missing</code>:不匹配条件</p>
</li>
<li><p><code>type</code>:拦截类型,可选<code>cookie</code>,<code>header</code>,<code>query</code></p>
</li>
<li><p><code>key/value</code>:键值对</p>
</li>
</ul>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/api-reference/file-conventions/proxy#matcher">请求器Matcher| Next.js</a></p>
<h3 id="处理跨域"><a href="#处理跨域" class="headerlink" title="处理跨域"></a>处理跨域</h3><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// ./src/proxy.ts</span><br><span class="hljs-keyword">import</span> {<span class="hljs-title class_">NextRequest</span>, <span class="hljs-title class_">NextResponse</span>} <span class="hljs-keyword">from</span> <span class="hljs-string">'next/server'</span>;<br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">ProxyConfig</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/server'</span>;<br><br><span class="hljs-keyword">const</span> corsHeaders = {<br> <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'*'</span>,<br> <span class="hljs-string">'Access-Control-Allow-Methods'</span>: <span class="hljs-string">'GET,POST,PUT,DELETE,OPTIONS'</span>,<br> <span class="hljs-string">'Access-Control-Allow-Headers'</span>: <span class="hljs-string">'Content-Type, Authorization'</span>,<br>};<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">proxy</span>(<span class="hljs-params"><span class="hljs-attr">request</span>: <span class="hljs-title class_">NextRequest</span></span>) {<br> <span class="hljs-keyword">const</span> response = <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">next</span>();<br> <span class="hljs-keyword">const</span> cookies = request.<span class="hljs-property">headers</span>.<span class="hljs-title function_">get</span>(<span class="hljs-string">'cookie'</span>) || <span class="hljs-string">''</span>;<br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(corsHeaders).<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">[key, value]</span>) =></span> {<br> response.<span class="hljs-property">headers</span>.<span class="hljs-title function_">set</span>(key, value);<br> });<br><br> <span class="hljs-keyword">if</span>(cookies){<br> <span class="hljs-keyword">return</span> response;}<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">NextResponse</span>.<span class="hljs-title function_">redirect</span>(<span class="hljs-keyword">new</span> <span class="hljs-title function_">URL</span>(<span class="hljs-string">'/'</span>, request.<span class="hljs-property">url</span>));<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(request.<span class="hljs-property">url</span>)<br>}<br><br><span class="hljs-comment">// matcher: '/home/:path*'</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {<br> <span class="hljs-attr">matcher</span>: <span class="hljs-string">'/proxy/:path*'</span><br>};<br></code></pre></td></tr></table></figure>
<h2 id="渲染基础"><a href="#渲染基础" class="headerlink" title="渲染基础"></a>渲染基础</h2><p>本章我们学习 <code>CSR</code> <code>SSR</code> <code>SSG</code> 三种渲染方式,以及<code>Hydration</code>水合的概念。</p>
<h3 id="CSR"><a href="#CSR" class="headerlink" title="CSR"></a>CSR</h3><p>CSR是<code>Client Side Rendering</code>的缩写,即客户端渲染。像我们使用的<code>Vue</code> <code>React</code> <code>Angular</code> 等框架,都是CSR。</p>
<p>工作流程如下:</p>
<p>浏览器请求服务器 -> 服务器返回HTML/JS/CSS等文件 -> JS动态渲染生成DOM -> 浏览器渲染DOM</p>
<pre><code class=" mermaid">flowchart TD
A[浏览器请求服务器] --> B[服务器返回HTML/JS/CSS文件]
subgraph C [浏览器处理]
C1[JS动态渲染生成DOM] --> C2[浏览器渲染DOM]
end
B --> C
C2 --> D[页面显示完成]
</code></pre>
<p>优点:</p>
<ul>
<li>交互流畅,可直接响应</li>
<li>前后端分离,前端注重UI,后端注重数据</li>
</ul>
<p>缺点:</p>
<ul>
<li>首屏加载慢,因为需要下载JS/CSS等文件</li>
<li>SEO不友好,因为JS动态渲染<code>(现在爬虫普遍已经支持JS抓取了)</code></li>
</ul>
<p>适合场景:</p>
<ul>
<li>后台管理系统开发(后台系统不需要SEO,也不需要首屏加载速度)</li>
<li>单页面应用开发(SPA)</li>
</ul>
<h3 id="SSR"><a href="#SSR" class="headerlink" title="SSR"></a>SSR</h3><p>SSR是<code>Server Side Rendering</code>的缩写,即服务端渲染。像我们使用的<code>Next.js</code> <code>Nuxt.js</code>等框架,都是SSR。</p>
<p>例如我们有一个电商网站,需要保证用户搜索关键词能搜到 <code>xx商品</code>, 还要注意 用户还可能是<code>弱网环境</code>,在地铁 电梯等,所以我们可以直接把API放到服务器请求,然后渲染成HTML页面返回给浏览器。</p>
<p>工作流程如下:</p>
<pre><code class=" mermaid">flowchart TD
A[浏览器请求服务器] --> B[服务器]
subgraph B [服务器处理]
B1[内部调用API接口] --> B2[渲染HTML页面]
end
B --> C[返回HTML页面到浏览器]
C --> D{浏览器并行处理}
D --> E[加载JS/CSS等文件]
D --> F[解析HTML页面]
E --> G[执行hydration<br/>水合过程]
F --> G
G --> H[页面完全交互就绪]
</code></pre>
<p>优点:</p>
<ul>
<li>首屏加载快,因为服务器已经渲染了HTML页面</li>
<li>SEO友好,搜索引擎能爬取到完整内容</li>
</ul>
<p>缺点:</p>
<ul>
<li>开发成本高,需要懂服务端知识,全栈开发。</li>
<li>服务器承担渲染工作,如果用户访问量大,对服务器配置要求高,增大成本</li>
</ul>
<p>适合场景:</p>
<ul>
<li>电商网站开发</li>
<li>博客网站开发</li>
<li>官网/首页等</li>
</ul>
<h3 id="SSG"><a href="#SSG" class="headerlink" title="SSG"></a>SSG</h3><p>SSG是<code>Static Site Generation</code>的缩写,即静态站点生成。像我们使用的<code>Vitepress</code> <code>Astro</code>等框架,都是SSG。</p>
<p>例如我们需要一个查看文档的网站,例如<code>Vue</code> <code>React</code> 等文档,大家看到的都是一样的,所以我们在构建的时候,直接编译成静态文件,连接口都不用请求了,如果在部署CDN/Nginx等服务器,基本可以实现秒开。</p>
<p>工作流程如下:</p>
<p>项目构建 <code>npm run build</code> -> 生成静态文件(每个路由对应一个 HTML) -> 部署到CDN/Nginx等服务器 -> 浏览器请求服务器 -> 服务器返回HTML页面 -> hydration</p>
<pre><code class=" mermaid">flowchart TD
subgraph A [构建阶段]
A1[项目构建 npm run build] --> A2[生成静态文件<br/>每个路由对应一个HTML]
end
A2 --> B[部署到CDN/Nginx等服务器]
subgraph C [请求响应阶段]
C1[浏览器请求服务器] --> C2[服务器返回预生成的HTML页面]
end
B --> C
C2 --> D[执行hydration<br/>水合过程]
D --> E[页面完全交互就绪]
</code></pre>
<p>优点:</p>
<ul>
<li>首屏加载极快(CDN 分发静态文件,无需服务器实时渲染)</li>
<li>服务器压力小(CDN 直接承载请求,无需服务器执行 JS)</li>
<li>SEO 最优(静态 HTML 含完整数据,搜索引擎爬取无压力)</li>
</ul>
<p>缺点:</p>
<ul>
<li>不适用于动态数据(数据更新需要重新构建部署,如实时股价、实时评论)</li>
<li>详情页面如果过多(构建时间会长)</li>
</ul>
<p>适合场景:</p>
<ul>
<li>技术文档</li>
<li>静态营销页</li>
<li>静态新闻站</li>
</ul>
<h3 id="Hydration-水合"><a href="#Hydration-水合" class="headerlink" title="Hydration(水合)"></a>Hydration(水合)</h3><p>简单来说就是HTML他是静态的,需要通过JS才能变成动态的,不然HTML是没有任何交互效果的,当JS下载完成在赋予HTML交互效果的阶段称之为<code>水合</code>。</p>
<p>以Next.js水合为例(详细版本):</p>
<p>服务端操作:</p>
<ul>
<li>Next.js 服务器接收到用户请求。</li>
<li>服务器执行 React 组件代码,获取数据(比如从 API 接口请求文章列表)。</li>
<li>服务器将 React 组件渲染成静态 HTML 字符串(包含了文章列表的所有内容)。</li>
<li>服务器将这个 HTML 字符串返回给浏览器。</li>
</ul>
<p>客户端操作:</p>
<ul>
<li>浏览器接收到 HTML,立即解析并展示给用户(此时用户能看到文章列表,但点击 “查看详情” 按钮没有反应)</li>
<li>浏览器开始下载页面所需的 JS 文件(包括 React 核心库、组件代码等)</li>
<li>JS 下载完成后,React 会执行 ReactDOM.hydrateRoot() 方法(在 React 18+ 中)</li>
<li>hydrateRoot() 会对比浏览器中的真实 DOM 和 React 组件的虚拟 DOM:<ul>
<li>如果结构一致,React 会给真实 DOM 绑定事件监听器。</li>
<li>如果发现差异(比如服务器和客户端数据不一致),React 会发出警告,并以客户端渲染的结果为准。</li>
</ul>
</li>
<li>水合完成后,页面变成可交互的动态页面(用户可以点击按钮、滚动加载更多内容等)</li>
</ul>
<pre><code class=" mermaid">flowchart TD
subgraph Server [服务端操作]
S1[Next.js服务器接收请求]
S2[执行React组件获取数据]
S3[渲染为静态HTML]
S4[返回HTML到浏览器]
S1 --> S2 --> S3 --> S4
end
S4 --> B[浏览器解析HTML<br/>显示静态内容<br/>无交互功能]
B --> C1[下载JS文件<br/>React/组件代码]
subgraph Hydration [React水合过程]
H1[执行hydrateRoot方法]
H2[对比DOM结构]
H3{结构一致?}
H4[绑定事件监听器]
H5[以客户端渲染为准]
H1 --> H2 --> H3
H3 -- 是 --> H4
H3 -- 否 --> H5
end
C1 --> Hydration
H4 --> F[页面完全可交互]
H5 --> FRSC(React Server Components)
</code></pre>
<h2 id="RSC-服务器组件"><a href="#RSC-服务器组件" class="headerlink" title="RSC(服务器组件)"></a>RSC(服务器组件)</h2><p>RSC(服务器组件)是React19<code>正式引入</code>的一种新的组件类型,它可以在服务器端渲染,也可以在客户端渲染。</p>
<p>像传统的<code>SSR</code>他是在服务器提前把页面渲染好,然后返回给浏览器,然后进行水合,<code>CSR</code>则是在客户端渲染,而<code>RSC</code>则是吸取两方优势,分为<code>服务器组件</code>和<code>客户端组件</code>。</p>
<p>举个例子:</p>
<p>例如我们有一个官网的页面,上面都是静态内容,但下面留言框是需要交互的。</p>
<p><img src="https://nextjs-docs-henna-six.vercel.app/7.gif" srcset="/img/loading.gif" lazyload alt="官网案例"></p>
<p>此时我们就可以拆分成两个组件:</p>
<ul>
<li><p>服务器组件: 上面都是静态内容,例如正文,标题,图片等,这类组件之所以适合在服务端执行,核心原因在于服务端渲染HTML+CSS的速度更快,生成的内容对搜索引擎完全可见,且无需客户端额外处理交互逻辑,完美匹配静态内容的需求。</p>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs tsx"> <span class="hljs-comment">//Next.js 默认服务器组件</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">HomePage</span>(<span class="hljs-params"></span>) {<br> <br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Home Page<span class="hljs-tag"></<span class="hljs-name">h1</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span><br> )<br> <br>}<br></code></pre></td></tr></table></figure>
</li>
<li><p>客户端组件: 下面留言框是需要交互的,例如交互功能,如点赞按钮、计数器、表单等。这类组件需要依赖浏览器DOM事件、状态管理(useState)、副作用(useEffect)等客户端能力,必须在客户端完成渲染和水合(即添加事件处理程序的过程)才能实现交互效果</p>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs tsx"> <span class="hljs-string">'use client'</span> <span class="hljs-comment">//声明这是一个客户端组件</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">HomePage</span>(<span class="hljs-params"></span>) {<br> <br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Home Page<span class="hljs-tag"></<span class="hljs-name">h1</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span><br> )<br> <br>}<br></code></pre></td></tr></table></figure></li>
</ul>
<h3 id="渲染-RSC-Payload"><a href="#渲染-RSC-Payload" class="headerlink" title="渲染(RSC Payload)"></a>渲染(RSC Payload)</h3><p>SSR模式是在服务器直接渲染成<code>HTML</code>页面,返回给浏览器的,而<code>RSC</code>他是一种特殊的紧凑的格式</p>
<p>那为什么这么做呢?因为我们的组件可以进行嵌套服务器组件嵌套客户端组件></p>
<p>黄色节点表示<code>服务器组件</code>,虚线节点表示<code>客户端组件</code> <img src="https://nextjs-docs-henna-six.vercel.app/_astro/2.ClwIpdJb_wEauX.webp" srcset="/img/loading.gif" lazyload alt="RSC渲染"></p>
<p>在这个结构中,Next.js就会标记哪些是客户端组件并且预留好位置,但是不会进行水合。</p>
<p>那么Next.js发现客户端组件也会在服务器生成这个结构,那干脆直接服务器里面把客户端组件进行预渲染(不包含交互),这样我们就能快速看到数据,等他加载完成后再进行水合,所以客户端组件也会在服务器进行一次<code>预渲染</code>。</p>
<h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul>
<li><p>将组件拆分成客户端组件和服务器组件,可以有效的减少<code>bundle</code>体积,因为<code>服务器组件</code>已经在服务器渲染好了,所以没必要打入<code>bundle</code>中,也就是说服务器组件所依赖的包都不会打进去,大大减少了<code>bundle</code>体积。</p>
</li>
<li><p>局部水合,像传统的SSR同构模式, 所有的页面都要在客户端进行水合,而<code>RSC</code>将组件拆分出来,只会把客户端组件进行水合,避免了全量水合带来的性能损耗。</p>
</li>
<li><p>流式加载,我们的HTML页面本来就支持流式加载,所以服务器组件可以边渲染边返回,提高了FCP(首次内容绘制)性能。</p>
</li>
</ul>
<h2 id="服务端组件与客户端组件"><a href="#服务端组件与客户端组件" class="headerlink" title="服务端组件与客户端组件"></a>服务端组件与客户端组件</h2><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// 服务端组件</span><br><span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'node:fs'</span> <span class="hljs-comment">//引入fs模块</span><br><span class="hljs-keyword">import</span> mysql, { <span class="hljs-title class_">RowDataPacket</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'mysql2/promise'</span> <span class="hljs-comment">//操作数据库 仅供演示 非最佳实践</span><br><span class="hljs-keyword">const</span> pool = mysql.<span class="hljs-title function_">createPool</span>({<br> <span class="hljs-attr">host</span>: <span class="hljs-string">'localhost'</span>,<br> <span class="hljs-attr">user</span>: <span class="hljs-string">'root'</span>,<br> <span class="hljs-attr">password</span>: <span class="hljs-string">'123456'</span>,<br> <span class="hljs-attr">database</span>: <span class="hljs-string">'catering'</span>,<br>})<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerPage</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-property">query</span><<span class="hljs-title class_">RowDataPacket</span>[]>(<span class="hljs-string">'SELECT * FROM goods'</span>)<br> <span class="hljs-keyword">const</span> data = fs.<span class="hljs-title function_">readFileSync</span>(<span class="hljs-string">'data.json'</span>, <span class="hljs-string">'utf-8'</span>)<br> <span class="hljs-keyword">const</span> json = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(data)<br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Server Page<span class="hljs-tag"></<span class="hljs-name">h1</span>></span></span><br><span class="language-xml"> {json.age}///{json.name}///{json.city}</span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">h3</span>></span>mysql<span class="hljs-tag"></<span class="hljs-name">h3</span>></span></span><br><span class="language-xml"> {rows.map((item: any) => (</span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{item.id}</span>></span>{item.name}-{item.goodsPrice}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span><br><span class="language-xml"> ))}</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span><br> )<br>}<br></code></pre></td></tr></table></figure>
<h3 id="服务端组件的优点"><a href="#服务端组件的优点" class="headerlink" title="服务端组件的优点"></a>服务端组件的优点</h3><ul>
<li><p>安全性:在服务端组件访问的API key不会暴露给客户端</p>
</li>
<li><p>体积:服务端组件在服务端渲染,所以不会打包到客户端</p>
</li>
<li><p>全栈:可以在服务端组件访问数据库,调用Nodejs的API,体积更小</p>
</li>
<li><p><strong>FCP</strong>:因为服务端组件是流式传输,提高了FCP的性能(看HTTP请求头<code>Transfer-Encoding: chunked</code>)</p>
</li>
</ul>
<h3 id="无法使用服务端组件的条件"><a href="#无法使用服务端组件的条件" class="headerlink" title="无法使用服务端组件的条件"></a>无法使用服务端组件的条件</h3><ol>
<li><p>需要交互性的场景</p>
<ul>
<li><p>事件处理:onClick、onChange、onSubmit 等</p>
</li>
<li><p>状态管理:useState、useReducer</p>
</li>
<li><p>生命周期副作用:useEffect、useLayoutEffect</p>
</li>
<li><p>DOM 引用:useRef 用于 DOM 元素</p>
</li>
</ul>
</li>
<li><p>浏览器专属 API</p>
</li>
<li><p>特定 React Hooks</p>
<ul>
<li>useState</li>
<li>useReducer</li>
<li>useEffect、useLayoutEffect</li>
<li>useImperativeHandle</li>
<li>自定义 hooks 依赖上述 hooks</li>
</ul>
</li>
<li><p>第三方库依赖浏览器环境</p>
<ul>
<li>图表库(如 D3.js、Chart.js)</li>
<li>地图库(如 Leaflet、Google Maps)</li>
<li>富文本编辑器</li>
<li>动画库(如 Framer Motion、GSAP)</li>
</ul>
</li>
<li><p>路由相关的 hooks: <code>useRouter()</code>、<code>usePathname()</code>、<code>useSearchParams()</code>.(使用 <code>navigation</code> 替代或移入客户端组件)</p>
</li>
<li><p>动态数据取决于客户端状态</p>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// ❌ 不能依赖实时客户端数据</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">Component</span>(<span class="hljs-params"></span>) {<br><span class="hljs-keyword">const</span> [time, setTime] = <span class="hljs-title function_">useState</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>()); <span class="hljs-comment">// 需要客户端</span><br><span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{time.toLocaleTimeString()}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;<br>}<br></code></pre></td></tr></table></figure></li>
</ol>
<h3 id="客户端组件"><a href="#客户端组件" class="headerlink" title="客户端组件"></a>客户端组件</h3><p>在<code>jsx/tsx</code>头部添加<code>'use client'</code>标志即可</p>
<p><strong>注:</strong> </p>
<ul>
<li><p>在浏览器环境下,首次加载客户端组件会自动的进行<strong>预渲染</strong>,尝试渲染DOM界面以备<code>React</code>水合,再次刷新或加载时则不会再进行预渲染。</p>
</li>
<li><p>客户端组件并不能嵌套服务端组件(原因是服务端组件可能调用了Node.js运行时或其他第三方依赖的函数,但浏览器并不具备与Node.js一致的运行时。)</p>
</li>
<li><p>部分API可能是通用的例如<code>fetch</code></p>
</li>
</ul>
<h3 id="区分服务端和客户端的fetch"><a href="#区分服务端和客户端的fetch" class="headerlink" title="区分服务端和客户端的fetch"></a>区分服务端和客户端的<code>fetch</code></h3><h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm i server-only<br></code></pre></td></tr></table></figure>
<h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">import</span> <span class="hljs-string">'server-only'</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">useTest</span>(<span class="hljs-params"><span class="hljs-attr">type</span>:<span class="hljs-number">0</span>|<span class="hljs-number">1</span></span>){<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">type</span> === <span class="hljs-number">0</span>){<br> <span class="hljs-keyword">return</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">'https://api.github.com'</span>)<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocket</span>(<span class="hljs-string">'wss://api.github.com'</span>)<br> }<br>}<br></code></pre></td></tr></table></figure>
<h2 id="缓存组件-Cache-Components"><a href="#缓存组件-Cache-Components" class="headerlink" title="缓存组件(Cache Components)"></a>缓存组件(Cache Components)</h2><h3 id="什么是Cache-Components"><a href="#什么是Cache-Components" class="headerlink" title="什么是Cache Components"></a>什么是Cache Components</h3><p>Cache Components 是Next.js(16)版本特有的机制,实现了<code>静态内容</code> <code>动态内容</code> <code>缓存内容</code>的混合编排。保留了静态内容的加载速度,又具备动态渲染的灵活性,解决了<code>静态内容(加载快但无法实时更新数据)</code>和<code>动态内容(加载慢但可以实时更新数据)</code>权衡的问题。</p>
<ul>
<li><p>静态内容: 构建(<code>npm run build</code>)时进行预渲染,例如 <code>「本地文件」「模块导入」「纯计算」(无网络请求、无用户相关数据)</code>,会被直接编译成<code>HTML</code>瞬间加载、立即响应。</p>
</li>
<li><p>动态内容:用户发起请求时才开始渲染的内容,依赖 “实时数据” 或 “用户个性化信息”,每次请求都可能生成不同结果,不会被缓存。例如<code>「实时数据源」(如实时接口、数据库实时查询)或「用户请求上下文」(如 Cookie、请求头、URL 参数)</code>。</p>
</li>
<li><p>缓存内容:缓存内容的本质就是缓存动态数据,缓存之后会被纳入<code>静态外壳(Static Shell)</code>,静态外壳就类似于<code>毛坯房</code>,会提前把结构搭建好,后续在通过(流式传输)填充里面的动态内容。</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>传统方案</th>
<th>Cache Components</th>
</tr>
</thead>
<tbody><tr>
<td>静态页面:数据无法实时更新</td>
<td>支持缓存内容重新验证,动态内容流式补充</td>
</tr>
<tr>
<td>动态页面:初始加载慢、服务器压力大</td>
<td>静态外壳优先返回,动态内容并行渲染</td>
</tr>
<tr>
<td>客户端渲染:bundle 体积大、首屏慢</td>
<td>服务器预渲染核心内容,客户端仅补充动态部分</td>
</tr>
</tbody></table>
<h3 id="启用方法"><a href="#启用方法" class="headerlink" title="启用方法"></a>启用方法</h3><p>须更新到<code>Next.js 16+</code> </p>
<p>在<code>next.cofig.ts</code>中 设置</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">NextConfig</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;<br><br><br><span class="hljs-keyword">const</span> <span class="hljs-attr">nextConfig</span>: <span class="hljs-title class_">NextConfig</span> = {<br> <span class="hljs-attr">cacheComponent</span>: <span class="hljs-literal">true</span>,<br>};<br><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> nextConfig;<br></code></pre></td></tr></table></figure>
<h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ul>
<li>对可能产生不同结果的动态内容如<code>Math.random()</code>,须使用<code>await connection()</code>避免缓存组件渲染错误,该组件<strong>必须</strong>被父组件<code><Suspense></Suspense></code>包裹。</li>
</ul>
<h3 id="缓存内容"><a href="#缓存内容" class="headerlink" title="缓存内容"></a>缓存内容</h3><p>对动态请求进行缓存,在动态请求的函数前一行使用<code>'use cache'</code>的注解字段。</p>
<p>如果需要预设缓存时间,可以调用<code>cacheLife(profile)</code>函数来制定缓存时间。</p>
<h4 id="cacheLife函数"><a href="#cacheLife函数" class="headerlink" title="cacheLife函数"></a><code>cacheLife</code>函数</h4><blockquote>
<p>引用地址:<a target="_blank" rel="external nofollow noopener noreferrer" href="https://nextjs.org/docs/app/api-reference/functions/cacheLife">Functions: cacheLife | Next.js</a></p>
</blockquote>
<p>缓存配置文件通过三个与时间相关的属性来控制缓存行为:</p>
<ul>
<li><p><code>stale</code>:客户端在未与服务器通信的情况下,可以使用缓存数据的最长时间</p>
</li>
<li><p><code>revalidate</code>:在这段时间之后,下一次请求会触发后台数据的更新(即数据的重新加载)</p>
</li>
<li><p><code>expire</code>: 如果在这段时间内没有收到任何请求,系统会等待新的请求到来,以便获取最新的内容</p>
</li>
</ul>