NextJs 初级篇 - 安装 | 路由 | 中间件
NextJs 初级篇 - 安装 | 路由 | 中间件
- 一. NextJs 的安装
- 二. 路由
- 2.1 路由和页面的定义
- 2.2 布局的定义和使用
- 2.3 模板的定义和使用
- ① 模板 VS 布局
- ② 什么是 use client
- 2.4 路由跳转的方式
- 2.5 动态路由
- 2.6 路由处理程序
- ① GET 请求的默认缓存机制
- ② 控制缓存或者退出缓存的手段
- ③ 控制缓存的时效 revalidate
- ④ 常见的编写问题
- 3. 中间件
一. NextJs 的安装
首先,NextJs要想使用,node版本不能太低,最低也要18以上,版本不够的,可以用nvm来管理,Mac用户的安装可以参考这篇文章:
mac 系统正确安装nvm
版本满足的同学,建议直接使用脚手架来创建nextJs,输入脚手架命令(这里又切了阿里的镜像,以防万一关闭SSL认证):
npm config set registry https://registry.npm.taobao.org
npm config set strict-ssl false
npx create-next-app@latest
结果如下:这里我们优先使用App Router(后面会讲解)

生成结果如下:

启动项目: npm run dev,之后访问:http://localhost:3000/ 即可
二. 路由
NextJs 拥有两套路由(两者兼容):
Pages Router:在pages目录下创建对应的文件或者目录即是一个路由。App Router:NextJs从版本13.4起的默认路由模式
为什么官方在新版本中,默认的路由模式采用了App呢?
pages下每个文件都会被当成路由,不符合开发习惯app架构新增了布局(layout)、模版(template)、加载状态(loading)、错误处理(error)、404 等文件,为项目开发提供了一套规范。
2.1 路由和页面的定义
我们这里主要讲官方更推荐的AppRouter,如图:

这里简单介绍下:
- 我们约定使用
page来代表一个页面,就好比React中使用:index。和React一样,默认导出个组件即可。 - 文件的路径就是对应的路由。
app/page.tsx对应路由/app/about/page.tsx对应路由/aboutapp/address/page.tsx对应路由/address- 后缀名可以使用:
.js、.jsx、.tsx
2.2 布局的定义和使用
首先布局组件有这么几个特征:
- 定义一个布局,需要新建一个名称为:
layout的固定文件,该组件接收一个children,代表子页面或者子布局。 - 布局可以嵌套,父布局中有一个子布局。
根布局还有额外几个特征:
- 定义在
app/layout.tsx文件中,会应用于所有的路由。并且此文件必须存在。 - 根布局文件中必须包含
html和body标签。同时其他布局不能包含这些标签。
根布局文件内容示例:
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export default function RootLayout({children,
}: Readonly<{children: React.ReactNode;
}>) {return (<html lang="en"><body className={inter.className}>{children}</body></html>);
}
布局的嵌套: address 下有个子目录home,每个文件都有自己的layout布局。

AddressLayout:
const AddressLayout = ({ children }: any) => {return (<><nav>我是Address的Layout</nav>{children}</>)
}
export default AddressLayout
HomeLayout:
const HomeLayout = ({ children }: any) => {return (<><nav>我是Home的Layout</nav>{children}</>)
}
export default HomeLayout
访问http://localhost:3000/address/home,呈现效果:

2.3 模板的定义和使用
模板跟布局的用法是一样的,例如我们在address下定义一个固定名称的模板文件:template.tsx
const AddressTemplate = ({ children }: any) => {return (<><nav>我是Address的模板</nav>{children}</>)
}
export default AddressTemplate
此时我们访问 http://localhost:3000/address/home,呈现效果:
这说明了:layout 会包裹 template,template 再包裹 page 。
那模板和布局之间,存在着什么差异?
① 模板 VS 布局
状态的维持:
- 模板:路由切换的时候,模板的内容会随之变更。
- 布局:路由切换的时候,布局的内容不会更改。
框架默认行为的更改:以组件Suspense为例,这个组件就是一个渲染fallback组件效果的功能。
- 模板:模板内使用
Suspense,每次切换路由的时候,都会展示fallback组件。 - 布局:模板内使用
Suspense,每次切换路由的时候,只会展示一次。
其实这两个点比较类似,说白了就是,每次在切换路由的时候,使用 template ,就可以重新触发渲染。
来个案例,目录结构如下:

sonA:
const SonA = () => {return <>我是SonA!!!</>
}
export default SonA
sonB:
const SonB = () => {return <>我是SonB!!!</>
}
export default SonB
父组件:page.tsx
import Link from "next/link";const Parent = () => {return <><nav>当前页面是父组件</nav><nav>跳转到:<Link href='/parent/sonA'>SonA</Link></nav><nav>跳转到:<Link href='/parent/sonB'>SonB</Link></nav></>
}export default Parent
template.tsx:
'use client'
import { useState } from "react"
const AddressTemplate = ({ children }: any) => {const [count, setCount] = useState<number>(0);return (<><h1>Template点击次数: {count}</h1><button onClick={() => setCount(count + 1)}>点击</button>{children}</>)
}
export default AddressTemplate
layout.tsx:
'use client'
import { useState } from "react"
const ParentLayout = ({ children }: any) => {const [count, setCount] = useState<number>(0);return (<><h1>Layout点击次数: {count}</h1><button onClick={() => setCount(count + 1)}>点击</button>{children}</>)
}
export default ParentLayout
结果如下:

② 什么是 use client
'use client' 是一个指令,放置在文件的顶部(通常是模块的第一行)。它告诉 Nextjs 该模块应该在客户端环境中执行,而不是在服务器端。
备注:默认情况下,NextJs中的页面是由服务端渲染的,即SSR。
2.4 路由跳转的方式
在上述案例中,我们使用 Link 组件来完成路由的跳转。而NextJs中,一共支持3种路由跳转的方式:
Link组件。- 使用钩子函数:
useRouter - 使用
redirect函数
第一种 Link 组件,是最为推荐的方式,例如上述案例中的代码。
<Link href='/parent/sonA'>SonA</Link>
除此之外,还可以更改默认的跳转行为:App Router 的默认行为是滚动到新路由的顶部。可以通过传参 scroll 为false改变。
<Link href='/parent/sonA' scroll={false}>SonA</Link>
第二种使用useRouter函数:
'use client'
import Link from "next/link";
import { useRouter } from 'next/navigation'const Parent = () => {const router = useRouter();return <><nav>当前页面是父组件</nav><nav>跳转到:<Link href='/parent/sonA'>SonA</Link></nav><nav>跳转到:<button onClick={() => router.push('/parent/sonB')}>SonB</button></nav></>
}export default Parent
我们还能发现,一旦使用钩子函数,这种客户端触发的动作,就需要加上标识'use client',代表它是需要客户端渲染。
第三种:redirect,用于给服务端组件用的,伪代码如下:
import { redirect } from 'next/navigation'async function login(id) {const res = await login()if (!res.ok) {redirect('/error')}return res.json()
}
2.5 动态路由
假设我们访问某个订单详情页,订单号可能是动态会变的,这里假设订单号是123456,一般我们可以设计两种URL:
/orderInfo/123456/orderInfo?orderId=123456
先说第一种,就需要通过动态路由的方式来访问。NextJs中,需要将文件夹的名字用方括号[] 扩住。例如我们创建一个目录:orderInfo/[orderId],代表orderId是一个动态路由,其中这个动态参数可以通过orderId字段来读取。

然后页面上我们希望把订单号展示出来。其中组件接收一个参数:params,如下:
import React from 'react'
const About = ({ params }: any) => {return <><h1>订单详情页面</h1><span>{params.orderId}</span></>
}
export default About
那么在访问 http://localhost:3000/orderInfo/123 的时候,效果如下:

那如果我们动态参数后还跟着路由,例如 http://localhost:3000/orderInfo/123/shop 若直接访问我们看看结果:

我们发现此时404了,可见这种情况下,动态参数[orderId]只会接收第一个路由片段,那怎么办呢?我们可以将[orderId] 改为: [[..orderId]],表示捕获所有后面所有的路由片段。

例如:

2.6 路由处理程序
NextJs中,对于客户端和服务端之间的API交互,叫做路由处理程序。
编写一个简单的路由处理程序,规则如下:
- 接口的路径跟路由比较相似,对应文件目录下创建一个名为
route的文件即可。 - 文件中需要导出固定名称的函数(无需使用
default export)GET、POST等。 - 可以使用
NextResponse代表返回的数据。
// route.js
export async function GET(request) {}
export async function HEAD(request) {}
export async function POST(request) {}
export async function PUT(request) {}
export async function DELETE(request) {}
export async function PATCH(request) {}
// 如果 `OPTIONS` 没有定义, Next.js 会自动实现 `OPTIONS`
export async function OPTIONS(request) {}
一个简单的GET和POST请求:

GET请求:
import { NextRequest, NextResponse } from 'next/server'export async function GET() {const data = [{ id: 1, name: 'LJJ' }]return NextResponse.json({ data })
}
对应结果:

POST请求:
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {const data = await request.json();return NextResponse.json(data)
}
对应结果:

① GET 请求的默认缓存机制
默认情况下,使用NextResponse对象的GET请求会被NextJs缓存起来。(本地模式下不会缓存,生产模式则会)
我们改一下我们的getUserList接口:
import { NextResponse } from 'next/server'export async function GET() {const data = [{ id: 1, name: 'LJJ', date: new Date().getMilliseconds() }]return NextResponse.json({ data })
}
然后跑命令: npm run build & npm run start
我们能发现构建产物:/api/getUserList 是静态的。

我们请求接口发现:无论请求几遍,时间戳永远不会变,是因为被缓存了。

② 控制缓存或者退出缓存的手段
退出缓存的方式有这么几种:
GET请求中使用了Request对象。例如:返回对象依赖于Request的参数。- 添加其他
HTTP方法,比如同一个route文件中,同时定义了GET和POST函数。 - 使用像
cookies、headers这样的动态函数。 - 也可以手动声明接口为动态模式。 文件顶端增加:
export const dynamic = 'force-dynamic'即可
// 返回结果依赖于request的参数
export async function GET(request: NextRequest) {const searchParams = request.nextUrl.searchParamsconst data = [{ id: 1, name: searchParams.toString(), date: new Date().getMilliseconds() }]return NextResponse.json({ data })
}
// 使用cookies这种动态函数
export async function GET(request) {const token = request.cookies.get('token')return Response.json({ data: new Date().toLocaleTimeString() })
}
③ 控制缓存的时效 revalidate
我们可以在route文件的顶部增加声明,例如以下就是标识缓存的有效期为10秒。
export const revalidate = 10
④ 常见的编写问题
首先是:获取URL上的参数、获取Header、获取Request请求体(JSON)、Cookie等
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {// 获取URL上的参数const searchParams = request.nextUrl.searchParamsconsole.log(searchParams.get('orderId'));// 处理 Headerconst headersList = new Headers(request.headers)const referer = headersList.get('myHeader')console.log(referer)// 获取Request请求体 (JSON)const data = await request.json();// 如果是FormData// const formData = await request.formData()// const name = formData.get('name')return NextResponse.json(data)
}
Postman请求:http://localhost:3000/api/getDetail?orderId=123

结果如下:

重定向:使用redirect即可。
import { redirect } from 'next/navigation'export async function POST(request: NextRequest) {redirect('https://www.baidu.com')
}
获取Cookie、并设置到Response中,则需要返回一个使用 Set-Cookie header 的 Response 实例
import { cookies } from 'next/headers'export async function POST() {const cookieStore = cookies()const token = cookieStore.get('test')console.log(token)return new Response('OK', {status: 200,headers: { 'Set-Cookie': `myNewToken=${12312312312}` },})
}
请求后:

3. 中间件
NextJs 中,中间件的定义,可以在根目录中定义一个固定名称的文件:middleware.ts,它的内容有两个部分组成:
// middleware.js
import { NextResponse } from 'next/server'// 第一部分:导出固定名称的中间件
export function middleware(request) {// ... 中间件逻辑
}
// 第二部分:设置匹配路径
export const config = {matcher: '/api/:path*',
}
这里直接给个案例,假设我中间件有多个功能:
- 中间件
headers:处理相关的Header - 中间件
logging:记录相关的日志
我们可以在根目录下创建个专门编写中间件的文件夹:middlewares

两个中间件内容:
// logging.ts
import { NextRequest } from "next/server";
export const logging = (next: any) => {return async (request: NextRequest) => {// ...console.log('logging')return next(request);};
};
// headers.ts
import { NextRequest } from "next/server";
export const headers = (next: any) => {return async (request: NextRequest) => {console.log('headers')return next(request);};
};
然后我们可以设置下别名,tsconfig.json 文件中:

这样就可以在真正设置中间件的地方(根路径的middleware.ts中)进行设置:
import { logging } from "@/middlewares/logging";
import { headers } from "@/middlewares/headers";
import { NextResponse } from "next/server";function chain(functions: any, index = 0) {const current = functions[index];if (current) {const next: any = chain(functions, index + 1);return current(next);}return () => NextResponse.next();
}export default chain([logging, headers]);export const config = {matcher: '/api/:path*',
}
这样访问任何一个接口,都会走相关的逻辑了。

相关文章:
NextJs 初级篇 - 安装 | 路由 | 中间件
NextJs 初级篇 - 安装 | 路由 | 中间件 一. NextJs 的安装二. 路由2.1 路由和页面的定义2.2 布局的定义和使用2.3 模板的定义和使用① 模板 VS 布局② 什么是 use client 2.4 路由跳转的方式2.5 动态路由2.6 路由处理程序① GET 请求的默认缓存机制② 控制缓存或者退出缓存的手…...
变分自动编码器(VAE)深入理解与总结
本文导航 0 引言1 起源1.1 自编码器的任务定义1.2 自编码器存在的问题1.3 VAE的核心思路 2 VAE的建模过程2.1 VAE的任务定义2.2 真实分布 ϕ \phi ϕ是什么,为什么要逼近这个分布的参数,如何做?2.3 “重参数化(Reparameterization…...
Leetcode 剑指 Offer II 079.子集
题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个整数数组 nums ,数组中的元素 互不相同 。返…...
Linux基础命令常见问题解决方案
Linux 基础命令常见问题解决方案 在Linux的日常使用中,用户经常会遇到各种各样的问题。本文旨在提供一个关于Linux基础命令的常见问题及其解决方案的全面指南。我们将覆盖30种不同的错误场景,并给出具体的解决步骤和示例,帮助初学者快速定位…...
LINQ(五) ——使用LINQ进行匿名对象初始化
总目录 C# 语法总目录 上一篇:LINQ(四) ——使用LINQ进行对象类型初始化 LINQ 五 ——使用LINQ进行匿名对象初始化 6.2 匿名类型 6.2 匿名类型 可以不用声明定义一个对象,直接使用new,然后直接赋值即可 string[] names { "Tom",…...
1小时从0开始搭建自己的直播平台(详细步骤)
本文讲述了如何从0开始,利用腾讯云的平台,快速搭建一个直播平台的过程。 文章目录 效果图详细步骤准备工作第一步:添加域名并检验cname配置1.先填加一个推流域名2. 点击完下一步,得到一个cname地址3. 将cname地址,配置…...
Python打包篇-exe
文章目录 pyinstallerauto-py-to-exe pyinstaller 命令行工具,语法自行查看官方help pip install pyinstallerauto-py-to-exe 基于pyinstaller的一款GUI工具,会自行打包py文件中依赖的库 pip install auto-py-to-exe auto-py-to-exe.exe //运行即可...
游戏找不到d3dcompiler_43.dll怎么办,教你5种可靠的修复方法
在电脑使用过程中,我们经常会遇到一些错误提示,其中之一就是“找不到d3dcompiler43.dll”。这个问题通常出现在游戏或者图形处理软件中,它会导致程序无法正常运行。为了解决这个问题,我经过多次尝试和总结,找到了以下五…...
如何使用多种算法解决LeetCode第135题——分发糖果问题
❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容,和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣! 推荐:数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航: LeetCode解锁100…...
泰拉瑞亚从零开始的开服教程
前言 本教程将讲诉使用Linux系统搭建泰拉瑞亚服务器(因为网上已经有很完善的windows开服教程了),使用的Linux发行版是Debian11,服务端使用的程序是TShock,游戏版本是1.4.4.9 所需要准备的 一台服务器(本教程使用的是…...
【云原生】K8s管理工具--Kubectl详解(一)
一、陈述式管理 1.1、陈述式资源管理方法 kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口kubectl 是官方的 CLI 命令行工具,用于与 apiserver 进行通信,将用户在命令行输入的命令,组织并转化为apiserver 能识…...
2024.5.26.python.exercise
# # 导入包 # from pyecharts.charts import Bar, Timeline # from pyecharts.options import LabelOpts, TitleOpts # from pyecharts.globals import ThemeType # # # 从文件中读取信息 # GDP_file open("1960-2019全球GDP数据.csv", "r", encoding&quo…...
代码随想录-Day20
654. 最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点,其值为 nums 中的最大值。 递归地在最大值 左边 的 子数组前缀上 构建左子树。 递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回 nums…...
揭秘C++ String容器:字符串操作的艺术
目录 编辑 引言 一、初识std::string:构造与初始化 二、字符串的操纵艺术:拼接、查找与替换 三、访问与遍历:字符的细腻触感 四、大小与容量:动态调整的智慧 五、进阶功能:探索更多可能 结语 引言 在C标准库…...
【C++】牛客 ——DP36 abb
✨题目链接: DP36 abb ✨题目描述 leafee 最近爱上了 abb 型语句,比如“叠词词”、“恶心心” leafee 拿到了一个只含有小写字母的字符串,她想知道有多少个 "abb" 型的子序列? 定义: abb 型字符串满足以下…...
SpringBoot如何实现跨域?
定义一个配置类,实现WebMvcConfigurer接口,重写addCorsMappings方法 Configuration public class CorsConfig implements WebMvcConfigurer {Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allow…...
SW 草图偏移 先预选
因为有些不能用链全部选,可以先框选要偏移的,再点偏移命令...
5.23 Linux中超时检测方式+模拟面试
1.IO多路复用的原理? IO多路复用使得一个或少量线程资源处理多个连接的IO事件的技术。对于要处理的多个阻塞的IO操作,建立集合并存储它们的文件描述符,利用单个阻塞函数去监控集合中文件描述符事件到达的情况,(如果到…...
MySQL数据表索引命名规范
在数据库设计和开发过程中,索引是提高查询性能的重要工具。合理的索引命名规范不仅能提高代码的可读性,还能便于维护和管理。本文将详细介绍MySQL数据表索引的命名规范,包括不同类型索引的命名方法,并提供多个代码示例以说明如何命…...
python内置函数map/filter/reduce详解
在Python中,map(), filter(), 和 reduce() 是内置的高级函数(实际是class),用于处理可迭代对象(如列表、元组等)的元素。这些函数通常与lambda函数一起使用,以简洁地表达常见的操作。下面我将分别解释这三个函数。 1. …...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
