这是一份快速参考备忘单,包含 Next.js 的 API 参考列表和一些示例
入门
创建项目
1 2 3 4 5
| npx create-next-app@latest # or yarn create next-app # or pnpm create next-app
|
或者创建 TypeScript 项目
1 2 3 4 5
| npx create-next-app@latest --typescript # or yarn create next-app --typescript # or pnpm create next-app --typescript
|
运行 npm run dev
或 yarn dev
或 pnpm dev
以在 http://localhost:3000 上启动开发服务器
添加首页
使用以下内容填充 pages/index.js
:
1 2 3 4 5
| function HomePage() { return <div>Welcome to Next.js!</div> }
export default HomePage
|
Next.js
是围绕页面的概念构建的。 页面是从 pages
目录中的 .js
、.jsx
、.ts
或 .tsx
文件导出的 React
组件
getServerSideProps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Page({ data }) { }
export async function getServerSideProps() { const res = await fetch(`https://.../data`) const data = await res.json()
return { props: { data } } }
export default Page
|
如果您从页面导出一个名为 getServerSideProps
(服务器端渲染)的函数,Next.js
将使用 getServerSideProps
返回的数据在每个请求上预渲染该页面
- 当您直接请求此页面时,
getServerSideProps
在请求时运行,此页面将使用返回的 props 进行预渲染
- 当您通过
next/link
或 next/router
在客户端页面转换上请求此页面时,Next.js
会向服务器发送 API 请求,服务器运行 getServerSideProps
getStaticPaths
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export async function getStaticPaths() { if (process.env.SKIP_BUILD_STATIC_GENERATION) { return { paths: [], fallback: 'blocking', } }
const res = await fetch('https://.../posts') const posts = await res.json()
const paths = posts.map((post) => ({ params: { id: post.id }, }))
return { paths, fallback: false } }
|
如果页面具有动态路由并使用 getStaticProps
,则需要定义要静态生成的路径列表
- 数据来自无头 CMS
- 数据来自数据库
- 数据来自文件系统
- 数据可以公开缓存(非用户特定)
- 页面必须预渲染(用于 SEO)并且速度非常快 ——
getStaticProps
生成 HTML 和 JSON 文件,这两种文件都可以由 CDN 缓存以提高性能
getStaticProps
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
| function Blog({ posts }) { return ( <ul> {posts.map((post) => ( <li>{post.title}</li> ))} </ul> ) }
export async function getStaticProps() { const res = await fetch('https://.../posts') const posts = await res.json()
return { props: { posts, }, } }
export default Blog
|
在服务器端的构建时被调用
增量静态再生
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
| function Blog({ posts }) { return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }
export async function getStaticProps() { const res = await fetch('https://.../posts') const posts = await res.json()
return { props: { posts, }, revalidate: 10, } }
export async function getStaticPaths() { const res = await fetch('https://.../posts') const posts = await res.json()
const paths = posts.map((post) => ({ params: { id: post.id }, }))
return { paths, fallback: 'blocking' } }
export default Blog
|
- 在初始请求之后和 10 秒之前对页面的任何请求也会被缓存和即时
- 在 10 秒窗口之后,下一个请求仍将显示缓存的(陈旧的)页面
- Next.js 在后台触发页面的重新生成
- 一旦页面生成成功,Next.js 将使缓存失效并显示更新后的页面。如果后台重新生成失败,旧页面仍将保持不变
使用 useEffect 客户端数据获取
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
| import { useState, useEffect } from 'react'
function Profile() { const [data, setData] = useState(null) const [isLoading, setLoading] = useState(false)
useEffect(() => { setLoading(true) fetch('/api/profile-data') .then((res) => res.json()) .then((data) => { setData(data) setLoading(false) }) }, [])
if (isLoading) return <p>Loading...</p> if (!data) return <p>No profile data</p>
return ( <div> <h1>{data.name}</h1> <p>{data.bio}</p> </div> ) }
|
使用 SWR 获取客户端数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import useSWR from 'swr'
const fetcher = (...args) => fetch(...args).then((res) => res.json())
function Profile() { const { data, error } = useSWR('/api/profile-data', fetcher)
if (error) return <div>Failed to load</div> if (!data) return <div>Loading...</div>
return ( <div> <h1>{data.name}</h1> <p>{data.bio}</p> </div> ) }
|
静态文件服务
Next.js 可以在根目录中名为 public
的文件夹下提供静态文件,如图像。 然后,您的代码可以从基本 URL (/
) 开始引用 public
中的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Image from 'next/image'
function Avatar() { return ( <Image src="/me.png" alt="me" width="64" height="64" /> ) }
export default Avatar
|
支持的浏览器和功能
Next.js 支持零配置的现代浏览器
- Chrome 64+
- Edge 79+
- Firefox 67+
- Opera 51+
- Safari 12+
Next.js 支持在 package.json
文件中配置 Browserslist
1 2 3 4 5 6 7 8 9
| { "browserslist": [ "chrome 64", "edge 79", "firefox 67", "opera 51", "safari 12" ] }
|
内置 CSS 支持
添加全局样式表
如果不存在,请创建一个 pages/_app.js
文件。 然后,导入 styles.css
文件
1 2 3 4 5 6 7 8
| import '../styles.css';
export default function MyApp({ Component, pageProps }) { return <Component {...pageProps} /> }
|
例如,考虑以下名为 styles.css
的样式表
1 2 3 4 5 6 7
| body { font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; }
|
从 node_modules 导入样式
对于全局样式表,如 bootstrap
或 nprogress
,您应该在 pages/_app.js
中导入文件
1 2 3 4 5 6 7 8
| import 'bootstrap/dist/css/bootstrap.css'
export default function MyApp({ Component, pageProps }) { return <Component {...pageProps} /> }
|
从 Next.js 9.5.4 开始,您的应用程序中的任何地方都允许从 node_modules
导入 CSS 文件
添加组件级 CSS (CSS Modules)
您无需担心 .error {} 与任何其他 .css
或 .module.css
文件!他将被生成 hash
名称
1 2 3 4
| .error { color: white; background-color: red; }
|
然后,创建 components/Button.js
,导入并使用上面的 CSS 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import styles from './Button.module.css'
export function Button() { return ( <button type="button" // 请注意“error”类 // 是如何作为导入的“styles”对象的属性访问的 className={styles.error} > Destroy </button> ) }
|
Sass 支持
Next.js 允许您使用 .scss
和 .sass
扩展名导入 Sass,可以通过 CSS 模块和 .module.scss
或 .module.sass
扩展名使用组件级 Sass
1
| $ npm install --save-dev sass
|
在使用 Next.js 的内置 Sass
支持之前,请务必安装 sass
自定义 Sass 选项
通过在 next.config.js
中使用 sassOptions
来实现配置 Sass
编译器。例如添加 includePaths
:
1 2 3 4 5 6 7 8
| const path = require('path')
module.exports = { sassOptions: { includePaths: [path.join(__dirname, 'styles')], }, }
|
Sass 变量
1 2 3 4 5 6
| /* variables.module.scss */ $primary-color: #64ff00;
:export { primaryColor: $primary-color; }
|
在 pages/_app.js
中导入 variables.module.scss
1 2 3 4 5 6 7 8 9
| import variables from '../styles/variables.module.scss'
export default function MyApp({ Component, pageProps }) { return ( <Layout color={variables.primaryColor}> <Component {...pageProps} /> </Layout> ) }
|
CSS-in-JS
最简单的一种是内联样式:
1 2 3 4 5 6 7
| function HiThere() { return ( <p style={{ color: 'red' }}>hi 这里</p> ) }
export default HiThere
|
使用 styled-jsx 的组件如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function HelloWorld() { return ( <div> Hello world <p>scoped!</p> <style jsx>{` p { color: blue; } div { background: red; } @media (max-width: 600px) { div { background: blue; } } `}</style> <style global jsx>{` body { background: black; } `}</style> </div> ) }
export default HelloWorld
|
当然,你也可以使用 styled-components
Layouts
基础示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| import Navbar from './navbar' import Footer from './footer'
export default function Layout({ children }) { return ( <> <Navbar /> <main>{children}</main> <Footer /> </> ) }
|
带有自定义应用程序的单一共享布局
1 2 3 4 5 6 7 8 9 10
| import Layout from '../components/layout'
export default function MyApp({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> ) }
|
使用 TypeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import type { ReactElement } from 'react' import Layout from '../components/layout' import NestedLayout from '../components/nested-layout' import type { NextPageWithLayout } from './_app'
const Page: NextPageWithLayout = () => { return <p>hello world</p> }
Page.getLayout = function getLayout(page: ReactElement) { return ( <Layout> <NestedLayout>{page}</NestedLayout> </Layout> ) }
export default Page
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
import type { ReactElement, ReactNode } from 'react' import type { NextPage } from 'next' import type { AppProps } from 'next/app'
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & { getLayout?: (page: ReactElement) => ReactNode }
type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout }
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />) }
|
每页布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Layout from '../components/layout' import NestedLayout from '../components/nested-layout'
export default function Page() { return ( ) }
Page.getLayout = function getLayout(page) { return ( <Layout> <NestedLayout>{page}</NestedLayout> </Layout> ) }
|
1 2 3 4 5 6
| export default function MyApp({ Component, pageProps }) { const getLayout = Component.getLayout || ((page) => page) return getLayout(<Component {...pageProps} />) }
|
数据请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import useSWR from 'swr' import Navbar from './navbar' import Footer from './footer'
export default function Layout({ children }) { const { data, error } = useSWR('/api/navigation', fetcher) if (error) return <div>Failed to load</div> if (!data) return <div>Loading...</div> return ( <> <Navbar links={data.links} /> <main>{children}</main> <Footer /> </> ) }
|
图片优化
本地图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import Image from 'next/image' import profilePic from '../public/me.png'
function Home() { return ( <> <h1>My Homepage</h1> <Image src={profilePic} alt="Picture of the author" // width={500} 自动提供 // height={500} 自动提供 // blurDataURL="data:..." 自动提供 // placeholder="blur" // 加载时可选的模糊处理 /> <p>Welcome to my homepage!</p> </> ) }
|
远程图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Image from 'next/image'
export default function Home() { return ( <> <h1>My Homepage</h1> <Image src="/me.png" alt="Picture of the author" width={500} height={500} /> <p>Welcome to my homepage!</p> </> ) }
|
要使用远程图像,src
属性应该是一个 URL
字符串,可以是相对的也可以是绝对的
Priority
您应该将优先级属性添加到将成为每个页面的 Largest Contentful Paint (LCP) 元素的图像。 这样做允许 Next.js 专门确定要加载的图像的优先级(例如,通过预加载标签或优先级提示),从而显着提高 LCP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Image from 'next/image'
export default function Home() { return ( <> <h1>My Homepage</h1> <Image src="/me.png" alt="Picture of the author" width={500} height={500} priority /> <p>Welcome to my homepage!</p> </> ) }
|
优化字体
Google 字体
自动托管任何 Google 字体。 字体包含在部署中,并从与您的部署相同的域提供服务。 浏览器不会向 Google 发送任何请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { Inter } from '@next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function MyApp({ Component, pageProps }) { return ( <main className={inter.className}> <Component {...pageProps} /> </main> ) }
|
指定粗细
如果不能使用可变字体,则需要指定粗细:
{5}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Roboto } from '@next/font/google'
const roboto = Roboto({ weight: '400', subsets: ['latin'], })
export default function MyApp({ Component, pageProps }) { return ( <main className={roboto.className}> <Component {...pageProps} /> </main> ) }
|
数组指定多个 weight 或 style
1 2 3 4 5
| const roboto = Roboto({ weight: ['400', '700'], style: ['normal', 'italic'], subsets: ['latin'], })
|
在 <head> 中应用字体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { Inter } from '@next/font/google' const inter = Inter({ subsets: ['latin'] }) export default function MyApp({ Component, pageProps }) { return ( <> <style jsx global>{` html { font-family: ${inter.style.fontFamily}; } `}</style> <Component {...pageProps} /> </> ) }
|
单页使用
1 2 3 4 5 6 7 8 9 10 11 12
| import { Inter } from '@next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function Home() { return ( <div className={inter.className}> <p>Hello World</p> </div> ) }
|
指定一个子集
1 2
| const inter = Inter({ subsets: ['latin'] })
|
在 next.config.js
中全局使用所有字体
1 2 3 4 5 6 7 8 9 10 11
| module.exports = { experimental: { fontLoaders: [ { loader: '@next/font/google', options: { subsets: ['latin'] } }, ], }, }
|
如果两者都配置,则使用函数调用中的子集
本地字体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import localFont from '@next/font/local'
const myFont = localFont({ src: './my-font.woff2' })
export default function MyApp({ Component, pageProps }) { return ( <main className={myFont.className}> <Component {...pageProps} /> </main> ) }
|
如果要为单个字体系列使用多个文件,src
可以是一个数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const roboto = localFont({ src: [ { path: './Roboto-Regular.woff2', weight: '400', style: 'normal', }, { path: './Roboto-Italic.woff2', weight: '400', style: 'italic', }, { path: './Roboto-Bold.woff2', weight: '700', style: 'normal', }, { path: './Roboto-BoldItalic.woff2', weight: '700', style: 'italic', }, ], })
|
使用 Tailwind CSS
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Inter } from '@next/font/google' const inter = Inter({ subsets: ['latin'], variable: '--font-inter', }); export default function MyApp({ Component, pageProps }) { return ( <main className={`${inter.variable} font-sans`}> <Component {...pageProps} /> </main> ) }
|
最后,将 CSS 变量添加到您的 Tailwind CSS 配置中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const { fontFamily } = require('tailwindcss/defaultTheme')
module.exports = { content: [ './pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', ], theme: { extend: { fontFamily: { sans: ['var(--font-inter)', ...fontFamily.sans], }, }, }, plugins: [], }
|
优化 Scripts
页面脚本
1 2 3 4 5 6 7 8 9 10 11
| import Script from 'next/script'
export default function Dashboard() { return ( <> <Script src="https://example.com/script.js" /> </> ) }
|
App 脚本
要为所有路由加载第三方脚本,导入 next/script
并将脚本直接包含在 pages/_app.js
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Script from 'next/script'
export default function MyApp({ Component, pageProps }) { return ( <> <Script src="https://example.com/script.js" /> <Component {...pageProps} /> </> ) }
|
将脚本卸载到 Web Worker(实验性的)
此策略仍处于试验阶段,只有在 next.config.js
中启用了 nextScriptWorkers
标志时才能使用:
1 2 3 4 5
| module.exports = { experimental: { nextScriptWorkers: true, }, }
|
设置完成后,定义 strategy="worker"
将自动在您的应用程序中实例化 Partytown
并将脚本卸载到网络工作者
1 2 3 4 5 6 7 8 9 10 11 12
| import Script from 'next/script'
export default function Home() { return ( <> <Script src="https://example.com/script.js" strategy="worker" /> </> ) }
|
其他属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Script from 'next/script'
export default function Page() { return ( <> <Script src="https://example.com/script.js" id="example-script" nonce="XUENAJFW" data-test="script" /> </> ) }
|
内联脚本
1 2 3
| <Script id="show-banner"> {`document.getElementById('banner').classList.remove('hidden')`} </Script>
|
1 2 3 4 5 6
| <Script id="show-banner" dangerouslySetInnerHTML={{ __html: `document.getElementById('banner').classList.remove('hidden')`, }} />
|
执行附加代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Script from 'next/script'
export default function Page() { return ( <> <Script src="https://example.com/script.js" onLoad={() => { console.log('Script has loaded') }} /> </> ) }
|
ESLint
集成 ESLint
1 2 3
| "scripts": { "lint": "next lint" }
|
然后运行 npm run lint
或 yarn lint
:
1 2 3 4 5 6 7 8
| yarn lint # 你会看到这样的提示: #
#
# 基本配置 # None
|
Strict
Strict 严格配置:包括 Next.js 的基本 ESLint 配置以及更严格的 Core Web Vitals 规则集
1 2 3
| { "extends": "next/core-web-vitals" }
|
Base 基础配置:包括 Next.js 的基本 ESLint 配置
项目的根目录中创建一个包含所选配置的 .eslintrc.json
文件
自定义设置
1 2 3 4 5 6 7 8
| { "extends": "next", "settings": { "next": { "rootDir": "packages/my-app/" } } }
|
rootDir
可以是路径(相对或绝对)、glob(即“packages/*/
”)或路径和/或 glob
数组
对自定义目录和文件进行检查
1 2 3 4 5
| module.exports = { eslint: { dirs: ['pages', 'utils'], }, }
|
在生产构建期间(next build
)仅在“pages
”和“utils
”目录上运行 ESLint
,或者使用命令
1
| $ next lint --dir pages --dir utils --file bar.js
|
禁用规则
您可以使用 .eslintrc
中的 rules
属性直接更改它们:
1 2 3 4 5 6 7
| { "extends": "next", "rules": { "react/no-unescaped-entities": "off", "@next/next/no-page-custom-font": "off" } }
|
修改或禁用受支持的插件(react
、react-hooks
、next
)提供的任何规则
Core Web Vitals
1 2 3
| { "extends": "next/core-web-vitals" }
|
Prettier
1 2 3
| npm install -S eslint-config-prettier
yarn add --dev eslint-config-prettier
|
1 2 3
| { "extends": ["next", "prettier"] }
|
lint-staged
1 2 3 4 5 6 7 8 9 10
| const path = require('path')
const buildEslintCommand = (filenames) => `next lint --fix --file ${filenames .map((f) => path.relative(process.cwd(), f)) .join(' --file ')}`;
module.exports = { '*.{js,jsx,ts,tsx}': [buildEslintCommand], }
|
内容添加到项目根目录中的 .lintstagedrc.js
文件中,以指定 --file
标志
TypeScript
create-next-app
1 2 3 4 5
| npx create-next-app@latest --ts
yarn create next-app --typescript
pnpm create next-app --ts
|
静态生成和服务端渲染
1 2 3 4 5 6 7 8 9 10
| import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next' export const getStaticProps: GetStaticProps = async (context) => { } export const getStaticPaths: GetStaticPaths = async () => { } export const getServerSideProps: GetServerSideProps = async (context) => { }
|
现有项目添加 ts 配置
您还可以通过在 next.config.js
文件中设置 typescript.tsconfigPath
属性来提供 tsconfig.json
文件的相对路径
API 路由
1 2 3 4 5 6 7 8 9 10
| import type { NextApiRequest, NextApiResponse } from 'next'
export default ( req: NextApiRequest, res: NextApiResponse ) => { res.status(200).json({ name:'John Doe' }) }
|
您还可以键入响应数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import type { NextApiRequest, NextApiResponse } from 'next'
type Data = { name: string }
export default ( req: NextApiRequest, res: NextApiResponse<Data> ) => { res.status(200).json({ name:'John Doe' }) }
|
自定义应用
使用内置类型 AppProps
并将文件名更改为 ./pages/_app.tsx
,如下所示:
1 2 3 4 5 6 7
| import type { AppProps } from 'next/app'
export default function MyApp({ Component, pageProps }: AppProps) { return <Component {...pageProps} /> }
|
类型检查 next.config.js
1 2 3 4 5 6 7 8 9
|
const nextConfig = { }
module.exports = nextConfig
|
忽略 TypeScript 错误
{3}1 2 3 4 5
| module.exports = { typescript: { ignoreBuildErrors: true, }, }
|
危险地允许生产构建成功完成,即使您的项目有类型错误
环境变量
加载环境变量
将环境变量从 .env.local
加载到 process.env
中
1 2 3
| DB_HOST=localhost DB_USER=myuser DB_PASS=mypassword
|
使用环境变量
1 2 3 4 5 6 7 8 9
| export async function getStaticProps() { const db = await myDB.connect({ host: process.env.DB_HOST, username: process.env.DB_USER, password: process.env.DB_PASS, }) }
|
自动扩展 .env* 文件中的变量
1 2 3 4
| HOSTNAME=localhost PORT=8080 HOST=http://$HOSTNAME:$PORT
|
如果您尝试使用实际值中带有 $
的变量,则需要像这样对其进行转义:\$
1 2 3 4 5 6 7 8
| A=abc
WRONG=pre$A
CORRECT=pre\$A
|
向浏览器公开环境变量
为了向浏览器公开变量,您必须在变量前加上 NEXT_PUBLIC_
前缀
1
| NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
|
NEXT_PUBLIC_ANALYTICS_ID
可以在此处使用,因为它的前缀是 NEXT_PUBLIC_
1 2 3 4 5 6 7 8 9 10 11 12
| import setupAnalyticsService from '../lib/my-analytics-service'
setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)
function HomePage() { return <h1>Hello World</h1> }
export default HomePage
|
路由
介绍
路由器将自动将名为 index
的文件路由到目录的根目录
:– |
– |
pages/index.js |
/ |
pages/blog/index.js |
/blog |
路由器支持嵌套文件。如果创建嵌套文件夹结构,文件将以同样的方式自动路由
:– |
– |
pages/blog/first-post.js |
/blog/first-post |
pages/dashboard/settings/username.js |
/dashboard/settings/username |
动态路由
:– |
– |
pages/blog/[slug].js |
/blog/:slug (/blog/hello-world ) |
pages/[username]/settings.js |
/:username/settings (/foo/settings ) |
pages/post/[...all].js |
/post/* (/post/2020/id/title ) |
具有动态路由的页面
如果您创建一个名为 pages/posts/[pid].js
的文件,那么它可以在 posts/1
、posts/2
等处访问
1 2 3 4 5 6 7 8 9 10
| import { useRouter } from 'next/router'
const Post = () => { const router = useRouter() const { pid } = router.query
return <p>Post: {pid}</p> }
export default Post
|
使用 useRouter
获取动态路由参数 pid
页面之间的链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import Link from 'next/link'
export default function Home() { return ( <ul> <li> <Link href="/">首页</Link> </li> <li> <Link href="/about">关于我们</Link> </li> <li> <Link href="/blog/hello-world"> 博文 </Link> </li> </ul> ) }
|
:– |
– |
/ |
pages/index.js |
/about |
pages/about.js |
/blog/hello-world |
pages/blog/[slug].js |
链接到动态路径
1 2 3 4 5 6 7 8 9
| import Link from 'next/link'
export default function Posts({ posts }) { return ( <Link href={`/blog/${encodeURIComponent(post.slug)}`}> 标题 </Link> ) }
|
URL 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Link from 'next/link'
export default function Posts({ posts }) { return ( <Link href={{ pathname: '/blog/[slug]', query: { slug: posts.slug }, }} > 标题 </Link> ) }
|
动态路由
考虑以下页面 pages/post/[pid].js
:
1 2 3 4 5 6 7 8 9 10
| import { useRouter } from 'next/router'
const Post = () => { const router = useRouter() const { pid } = router.query
return <p>Post: {pid}</p> }
export default Post
|
到动态路由的客户端导航由 next/link
处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Link from 'next/link'
export default function Home() { return ( <div> <Link href="/post/abc"> 转到 pages/post/[pid].js </Link> <Link href="/post/abc?foo=bar"> 也转到 pages/post/[pid].js </Link> <Link href="/post/abc/a-comment"> 转到 pages/post/[pid]/[comment].js </Link> </div> ) }
|
多个动态路由
工作方式相同。 页面 pages/post/[pid]/[comment].js
将匹配路由 /post/abc/a-comment
并且它的查询对象将是:
1
| { "pid": "abc", "comment": "a-comment" }
|
捕捉所有路由
可以通过在括号内添加三个点 (...
) 来扩展动态路由以捕获所有路径,pages/post/[...slug].js
匹配 /post/a
,也匹配 /post/a/b
、/post/a/b/c
等
1 2 3 4 5
| { "slug": ["a"] }
{ "slug": ["a", "b"] }
|
可选捕获所有路由
使用 [[...slug]]
,pages/post/[[...slug]].js
将匹配 /post
、/post/a
、/post/a/b
等
1 2 3 4 5 6
| { }
{ "slug": ["a"] }
{ "slug": ["a", "b"] }
|
事件执行调整页面
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { useRouter } from 'next/router'
export default function ReadMore() { const router = useRouter()
return ( <button onClick={() => router.push('/about')} > 点击这里阅读更多 </button> ) }
|
浅路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useEffect } from 'react' import { useRouter } from 'next/router'
export default function Page() { const router = useRouter()
useEffect(() => { router.push('/?counter=10', undefined, { shallow: true }) }, [])
useEffect(() => { }, [router.query.counter]) }
|
注意事项
浅路由仅适用于当前页面中的 URL 更改。 例如,假设我们有另一个名为 pages/about.js
的页面,并且您运行以下命令:
1
| router.push('/?counter=10', '/about?counter=10', { shallow: true })
|
由于这是一个新页面,它会卸载当前页面,加载新页面并等待数据获取,即使我们要求进行浅层路由
另见