yarn 与 node 16.14
npx create-next-app
然后像选配一样选择项目需要一起初始化的工具之类的,可以根据项目所需选择需要的就行了,这边是全部都选了
需要注意的是下面这个选项,可能会导致项目结构不一致,他会问你是否需要创建以 src/ 为根目录的项目结构, 这边也选了
? Would you like to use `src/` directory with this project? » No / Yes
这个选项是用来决定是否使用 src/ 目录作为项目根目录
选择 Yes 会将 src/ 目录作为项目根目录,选择 No 则使用默认的项目结构,将 pages/、public/、styles/ 等目录作为项目根目录。
还有这个选项,与路由相关的,这边也是选了 Yes
? Use App Router (recommended)? » No / Yes
选择 Yes 会使用 Next.js 推荐的路由方案,选择 No 则需要手动配置路由。
使用 App Router 是 Next.js 推荐的路由方案,它提供了一些特性,例如动态路由、嵌套路由、自动代码分割等。使用 App Router 可以让你更方便地实现页面跳转和路由控制,同时也可以提高页面的性能和加载速度。
总的来说,选择 Yes 可以让你更快地创建一个 Next.js 应用,并使用 Next.js 推荐的路由方案。而选择 No 可以让你更加灵活地控制路由行为,但需要手动配置路由。
最后得到一个这样的目录结构~
next-app
├─ public # 存放静态资源的目录
│ ├─ next.svg # Next.js 的 logo
│ └─ vercel.svg # Vercel 的 logo
├─ src # 存放代码位置与初始生成是否选择 src 有关
│ └─ app # 应用程序的代码目录
│ ├─ favicon.ico # 网站的图标
│ ├─ globals.css # 全局样式
│ ├─ layout.tsx # 页面布局组件
│ └─ page.tsx # 页面组件
├─ next-env.d.ts # Next.js 生成的类型声明文件
├─ next.config.js # Next.js 的配置文件
├─ package-lock.json # 锁定依赖包的版本信息
├─ package.json # 项目的依赖包信息
├─ postcss.config.js # PostCSS 相关配置
├─ README.md # 项目的说明文档
├─ tailwind.config.js # Tailwind CSS 相关配置
└─ tsconfig.json # TypeScript 相关配置
接下来就是下依赖启动了,可以看到 pageage.json 里使用 dev 命令触发 next dev
的
yarn
yarn dev
然后默认启动了 http://localhost:3000/
为本地服务,点击打开就可以看到一个 next
的页面 demo
直观的、 基于页面 的路由系统(并支持 动态路由)
基于页面就是会基于 pages
文件下中导出得 .js
、jsx
、.ts
或 .tsx
的 React
组件 每个 page
都使用其文件名作为路由
所以创建一个 pages
目录,然后在里面创建个 home.tsx
,写入一个 React
组件并 export default
它
export default function Home(props: any) {
return <h1>Home</h1>;
}
然后访问 http://localhost:3000/home
就可以看到效果了
基础用法
例如在 pages
下创建一个 [uid].tsx
文件作为路由,那么你就可以通过 pages/123
、pages/321
这样进行访问路由。
然后是要 next
提供的 useRouter
可以获取到路由的 query
参数
// pages/[uid].tsx
import { useRouter } from "next/router";
export default function UidPage(props: any) {
const { query } = useRouter();
return <h1>uid: {query.uid}</h1>;
}
动态路由再携带其他参数
如 http://localhost:3000/999?test=test
, 这先是命中了 [uid].tsx
的页面,然后 ? 后的路由参数会存储在 useRouter
的 query
里
// pages/[uid].tsx
import { useRouter } from "next/router";
export default function UidPage(props: any) {
const { query } = useRouter();
console.log(query, "query"); // 会在控制台输出 { uid:999,test:"test" }
return <h1>uid: {query.uid}</h1>;
}
与 page
同名的参数名,会被 page
覆盖,如 http://localhost:3000/999?uid=123
这样访问,查询参数会被路由参数覆盖
console.log(query, "query"); // 会在控制台输出 { uid:999}
多层动态路由
在 pages/about
目录下创建 [uuid]
目录,然后得到 pages/about/[uuid]
,再继续在里面创建 [cid].tsx
,得到一个pages/about/[uuid]/[cid].tsx
的路由路线
访问 http://localhost:3000/about/666/666
进入到pages/about/[uuid]/[cid].tsx
路由
得到以下路由query
参数
console.log(query, "query"); // 会在控制台输出 {uuid: '666', cid: '666'}
中间还有一层 pages/about/[uuid]
, 想要命中的话 需要在 [uuid]
目录下创建个 index.tsx
, 这样访问 http://localhost:3000/about/666
就可以命中 uuid
console.log(query, "query"); // 会在控制台输出 {uuid: '666'}
捕获所有路由
利用...
扩展运算符来创建路由
在 pages/catch
目录下创建 [...params].tsx
文件,然后得到 pages/catch/[...params].tsx
的路由
无论是 /catch/6
还是 /catch/6/7
或者 /catch/6/7/8
, 都会被[...params].tsx
捕获到
query
数据会以数组形式呈现
// /catch/6
console.log(query, "query"); // 会在控制台输出 {"params":["6"]}
// /catch/6/7
console.log(query, "query"); // 会在控制台输出 {"params":["6","7"]}
// /catch/6/7/8
console.log(query, "query"); // 会在控制台输出 {"params":["6","7","8"]}
可选捕获所有路由
通过将参数包含在双括号 [[...params]]
中,可以使捕获所有路由成为可选的。
例如,pages/optional/[[...params]].ts
将匹配/optional
、/optional/a
、/optional/a/b
等。
像上面得捕获所有路由的是进如 /catch
路由后如果 pages 下没有 一个动态路由文件 则会 404, 而可选捕获则不会造成 404 原因
区别
[...params].tsx
会捕获所有路由,但是没有路由参数的会造成 访问/catch
会 404[[...params]].tsx
不会造成 404,可选的意思是他的参数不是必传的,也就是不传参数,也能进到/optional
页面
预渲染有两种模式
SSG(Static Site Generation)
静态站点生成SSR(Server Side Rendering)
服务端渲染 也被称为 动态渲染
在预渲染中,服务器在构建时生成静态 HTML 文件,包含了页面的基本结构、样式和部分静态内容。 当用户请求页面时,服务器直接返回这个已经生成好的静态 HTML 文件,客户端只需要进行少量的交互和数据绑定,从而快速展示页面。
预渲染 与 客户端渲染CSR(Client Side Rendering)
的区别
预渲染 | 客户端渲染 |
---|---|
返回生成好的 HTML 文件包含结构与样式 | 返回空 HTML 由客户端 JS 加载剩下的续结构与样式 |
在服务端做进行网络请求 | 在客户端进行网络请求 |
在服务端或者构建时进行页面渲染 | 在客户端浏览器上进行页面渲染 |
首次加载速度快 | 首次加载时间较长 |
数据与交互都要刷新页面 | 更丰富的交互和实时数据更新的能力 |
所以预渲染优点就是
- 减少客户端压力,如计算与网络请求
- 提高了页面的加载速度
而 Next.js
提供了两种预渲染方式
SSG
使用getStaticProps
SSR
使用getServerSideProps
在 next
中,要使页面使用SSG 静态生成
,只需导出(export)
页面组件与导出(export)
getStaticProps
函数
HTML 是在 构建时(build time) 生成的
在 pages
下创建名为 ssg
的目录,然后创建 index.tsx
路由,最后得到 pages/ssg/index.tsx
书写 SSG 代码,先写个模板
// `pages/ssg/index.tsx`
export default function SSGDemoPage() {
return <>SSGDemoPage</>;
}
export async function getStaticProps() {
return {
props: {},
};
}
当然现在访问 http://localhost:3000/ssg 只能看到一个 SSGDemoPage 的文字
试着自己填充点数据在getStaticProps
中
// `pages/ssg/index.tsx`
export default function SSGDemoPage(props: any) {
console.log(props);
return (
<>
<h1> SSGDemoPage</h1>
{props.testData}
</>
);
}
export async function getStaticProps() {
return {
props: {
testData: 666,
},
};
}
可以在页面与控制台中看到 props.testData
的数据
所以 getStaticProps
返回值会填充到组件的 props 中,而 getStaticProps
可以在服务端请求网络直接进行数据注入,数据注入也叫水合(data hydration)
来做个请求,在 getStaticProps
请求知乎的世界各国大区接口https://www.zhihu.com/api/v3/oauth/sms/supported_countries
// `pages/ssg/index.tsx`
export default function SSGDemoPage(props: any) {
const { list } = props;
console.log(list, "list");
return (
<>
<h1>SSGDemoPage</h1>
<ul>
{list.map((item: any) => (
<li key={item.abbr + item.code}>
{item.abbr + " " + item.code + " " + item.name}
</li>
))}
</ul>
</>
);
}
// ssg 此函数在构建时被调用
export async function getStaticProps() {
const res = await fetch(
"https://www.zhihu.com/api/v3/oauth/sms/supported_countries"
);
// 返回的数据需要经过.json() 处理
const { data = [] } = await res.json();
return {
props: {
list: data,
},
};
}
然后就可以看到页面循环出来了请求的数据,控制台也能看到
在 next
中,要使页面使用SSR 服务端渲染
,只需导出(export)
页面组件或导出(export)
getServerSideProps
函数
HTML 是在每个页面请求时生成的,注意是每次
在 pages
下创建名为 ssr
的目录,然后创建 index.tsx
路由,最后得到 pages/ssr/index.tsx
,直接 copy 刚才的 ssg 吧,反正改个渲染模式就行了
只是把 getStaticProps
换成了 getServerSideProps
, 还是请求某乎的接口来渲染
// pages/ssr/index.tsx
import Layout from "../../app/layout";
export default function SSRDemoPage(props: any) {
const { list } = props;
console.log(list, "list");
return (
<Layout>
<h1>SSRDemoPage</h1>
<ul>
{list.map((item: any) => (
<li key={item.abbr + item.code}>
{item.abbr + " " + item.code + " " + item.name}
</li>
))}
</ul>
</Layout>
);
}
// ssr
export async function getServerSideProps(context: any) {
const res = await fetch(
"https://www.zhihu.com/api/v3/oauth/sms/supported_countries"
);
const { data = [] } = await res.json();
return {
props: {
list: data,
},
};
}
对比 SSG 的话,SSR 非常依赖服务器,因为是每次请求重新生成的,会增加响应速度
-
params: 一个包含动态路由参数的对象。如果你的页面具有动态路由,那么这个对象将包含从路由中提取的参数。例如,对于路由/posts/[id],params 可能是 { id: '1' }。
-
query: 一个包含查询字符串参数的对象。例如,对于路由/search?keyword=nextjs,query 可能是 { keyword: 'nextjs' }。
-
req: HTTP 请求对象,包含有关传入请求的信息。
-
res: HTTP 响应对象,包含有关将发送至客户端的响应的信息。
log 一下 params, query 看看吧,注意这个函数是在服务端执行的,所以 log 可以在终端看到
// pages/index.tsx 的 ssr
export async function getServerSideProps(context: any) {
const { params, query } = context;
console.log(params, query, "params, query");
return {
props: {},
};
}
访问 http://localhost:3000/
,然后生成页面时候执行getServerSideProps
函数
可以看到控制台 log 出了两个参数的信息,因为没用命中动态路由,url 也没 query
参数~
那么再次访问 http://localhost:3000?page=123
,再去控制台看
可以看到 query
参数了,但是 params 还是没值
访问一个有动态路由的页面,之前建立了 pages/about/[uuid]
目录, 访问这个地址 http://localhost:3000/about/123
,对应页面 pages/about/[uuid]/index.tsx
然后可以看到 params
与 query
都获取到了动态路由的值
再访问个多层动态路由的 , http://localhost:3000/about/123/321
,对应页面 pages/about/[uuid]/[cid].tsx
可以看到 params
与 query
把两层路由的值都输出再控制台了
还有很多形参但是感觉不常用,需要时再去翻文档把
而 res
则是设置响应信息了,req
则是获取请求信息,
getServerSideProps
能函数在服务器端执行,所以可以在其实现中访问服务器端的数据库或其他资源,这样的话 res
与 req
则显得很有用处~
但是 getStaticProps
通常在构建时执行,而不是在服务器上。
export async function getServerSideProps(context) {
const { req, res } = context;
// 获取请求的 URL
const url = req.url;
// 获取请求头中的信息
const userAgent = req.headers["user-agent"];
// 设置响应头
res.setHeader("Content-Type", "application/json");
// 设置响应状态码
res.status(200);
// 发送响应内容
res.send({ message: "Hello, world!" });
// ...
}
- preview:preview 是 true 页面是否处于预览模式,false 否则。请参阅预览模式文档。
- previewData: 所设置的预览数据 setPreviewData。请参阅预览模式文档。
- resolvedUrl:请求 URL 的规范化版本,它去除了_next/data 客户端转换的前缀并包含原始查询值。
- locale 包含活动区域设置(如果您已启用国际化路由)。
- locales 包含所有受支持的区域设置(如果您已启用国际化路由)。
- defaultLocale 包含配置的默认区域设置(如果您已启用国际化路由)。
需要 return
一个对象
getServerSideProps
- props: 传给组件的 props
- notFound: 标记该页面为未找到状态,意味着虽然页面已命中,但数据获取或其他条件可能导致页面无法正确渲染,从而触发展示 404 页面的逻辑
- redirect: 一个对象包含两个值 destination 与 permanent;返回这个对象时,服务器会在处理请求时执行重定向操作,将用户导航到指定的页面
- destination:指定重定向的目标页面路径
- permanent:一个布尔值,指示重定向是否是永久性的,
false
为临时重定向,true
为永久
- error: 一个对象,服务器发现错误时的情况
- statusCode: 错误码,比如设置 500 ,则服务器发现错误会定向到
pages/500.tsx
getStaticProps
- props: 传给组件的 props
- revalidate: 设置页面的重新生成的时间间隔(以秒为单位),过期了则会重新生成
// ssr
export async function getServerSideProps(context: any) {
// 从服务器端获取数据并返回
return {
props: {
// 数据对象
},
// 可选的属性
notFound: true,
redirect: {
destination: "/another-page",
permanent: false,
},
error: {
statusCode: 500,
},
};
}
// ssg
export async function getStaticProps(context: any) {
// 预先获取静态数据并返回
return {
props: {
// 静态数据对象
},
// 可选的属性
revalidate: 60, // 重新生成页面的频率(以秒为单位)
};
}
- 在 pages 下创建 _app.tsx 做入口根组件
// pages/_app.tsx
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
这个也是一个全局的 APP 组件,在官网中它说可以做很多事情
- 页面切换之间保持布局的持久化,(比如一些固定的侧边栏,不变的固定布局)
- 切换页面时保持状态(state),(想不到什么好实践,感觉鸡肋)
- 使用 componentDidCatch 自定义错误处理, (app 组件使用 class 组件即可)
- 向页面(pages)注入额外的数据,( 从 Component 组件传 props)
- 添加全局 CSS,(在 app 引入 global.css)
// pages/_app.tsx
import { createContext } from "react";
import type { AppProps } from "next/app";
import "../app/globals.css";
export const GlobalContext = createContext({});
export default function App({ Component, pageProps }: AppProps) {
return (
<GlobalContext.Provider value={{ state: "active" }}>
<Component {...pageProps} />
</GlobalContext.Provider>
);
}
注:App 不支持 getStaticProps
或 getServerSideProps
前置知识ES2020
import()
使用 next
提供的 dynamic
, 类似于React.lazy
dynamic 返回的默认组件。它的工作方式类似于常规的 React 组件,可以像平常一样向它传递 props
使用案例,新建app/components/TestCop
组件然后通过 dynamic 加 improt()
import dynamic from "next/dynamic";
const TestCop = dynamic(import("../app/components/TestCop"));
export default function PageIndex(props: any) {
return (
<>
<TestCop />
</>
);
}
上面的是默认导出的,再看看非默认导出
// 新建一个组件,使用 export 导出不加default
import React from "react";
export function TestCop2() {
return <div>TestCop2</div>;
}
引入, improt 返回的是 Promise,从 resolve 里能去到 TestCop2 的值,并返回
import dynamic from "next/dynamic";
const TestCop = dynamic(import("../app/components/TestCop"));
const TestCop2 = dynamic(
import("../app/components/TestCop2").then((res) => res.TestCop2)
);
export default function PageIndex(props: any) {
return (
<>
<TestCop />
<TestCop2 />
</>
);
}
dynamic 还可以接受多个参数
import dynamic from "next/dynamic";
const TestCop = dynamic(import("../app/components/TestCop"));
export default function PageIndex(props: any) {
return (
<>
<TestCop />
</>
);
}
这是一个示例的 next.config.js
文件,其中包含了前面提到的 29 个配置选项,官网配置
module.exports = {
// 设置静态资源的前缀路径
assetPrefix: "/static",
// 设置应用程序的基本路径
basePath: "/",
// 启用或禁用服务器端压缩响应的gzip选项
compress: true,
// 配置开发环境下的构建指示器
devIndicators: {
buildActivity: true,
autoPrerender: false,
},
// 配置构建输出目录
distDir: "build",
// 设置环境变量,供客户端和服务器端代码使用
env: {
CUSTOM_VARIABLE: "custom value",
},
// 配置 ESLint 的选项
eslint: {
// ESLint 配置选项
},
// 自定义导出静态页面的路径映射
exportPathMap: async (
defaultPathMap,
{ dev, dir, outDir, distDir, buildId }
) => {
// 导出路径映射逻辑
return {
"/": { page: "/" },
"/about": { page: "/about" },
};
},
// 自定义生成的构建 ID
generateBuildId: () => {
return "custom-build-id";
},
// 配置是否生成 ETags 来支持缓存
generateEtags: true,
// 配置自定义响应头
headers: () => {
return [
{
source: "/(.*)",
headers: [
{
key: "Cache-Control",
value: "max-age=3600",
},
],
},
];
},
// 配置HTTP请求代理选项
httpAgentOptions: {
// 请求代理选项
},
// 配置 Next.js 图片优化功能
images: {
// 图片优化选项
},
// 配置是否在开发环境下按需编译页面
onDemandEntries: {
// 按需编译选项
},
// 配置构建输出选项
output: {
// 构建输出选项
},
// 配置页面文件扩展名
pageExtensions: ["js", "jsx", "ts", "tsx"],
// 配置是否显示 "X-Powered-By" 响应头
poweredByHeader: true,
// 配置是否在生产环境中启用浏览器端源映射
productionBrowserSourceMaps: false,
// 配置是否启用 React 严格模式
reactStrictMode: true,
// 配置自定义重定向规则
redirects: () => {
return [
{
source: "/old-path",
destination: "/new-path",
permanent: true,
},
];
},
// 配置自定义重写规则
rewrites: () => {
return [
{
source: "/api/:path*",
destination: "/api-handler",
},
];
},
// 配置运行时配置,用于在服务器端和客户端之间共享数据
runtimeConfig: {
// 运行时配置选项
},
// 配置是否添加末尾斜杠
trailingSlash: false,
// 配置需要在构建过程中进行转译的包
transpilePackages: ["package1", "package2"],
// Turbopack 相关
turbo: {},
// 配置 Next.js 以支持 TypeScript
typescript: {
// TypeScript 配置选项
},
// 配置是否启用 URL 导入
urlImports: {
// URL 导入选项
},
// 自定义 Webpack 配置
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// Webpack 配置逻辑
return config;
},
// 配置是否添加Web Vitals的信息
webVitalsAttribution: true,
};
-
next/router
:Next.js 的路由模块,用于处理客户端的路由导航。它提供了一组 API,可以通过编程方式导航到不同的页面,例如使用router.push()
或router.replace()
还有useRouter
。 -
next/link
:用于在 Next.js 应用程序中创建导航链接的组件。它会自动处理页面的预加载,优化用户的页面导航体验,并避免完全刷新页面。 -
next/image
:优化的图片组件,用于在 Next.js 应用中加载和展示图像。它可以自动根据设备和浏览器优选最佳图片格式和大小,以提高页面性能。 -
next/script
:用于在 Next.js 应用程序中加载外部脚本的组件。它可以确保在页面加载完成后再加载脚本,以优化应用程序的性能和性能指标。 -
next/head
:用于在 Next.js 应用程序中定义文档头部内容的组件。可以在页面级别设置标题、元标记和其他文档头信息。 -
next/amp
:Next.js 的 AMP (Accelerated Mobile Pages) 模块,用于创建和优化 AMP 页面,以提供更快的加载速度和更好的移动端用户体验。 -
next/server
:Next.js 服务器端渲染相关的模块。它包含用于自定义服务器和服务器端数据获取的工具。
这些 Next.js 模块为开发者提供了更便捷的工具和组件,以构建高性能的、可维护的、现代化的 React 应用程序。它们可以使开发者更专注于业务逻辑,而不必过多关注底层的配置和优化细节。