这是关于 NextJS 一些鲜为人知的功能的指南。
我在本文中包含了我在我开发的 NextJS 应用程序中未见过的内容。 也许这些功能比我意识到的更受欢迎,但无论如何,希望您遇到一些对您来说是新的功能。 如果您有建议,请发表评论。
本文假设您熟悉 NextJS(页面和应用程序路由器),我不会在这里讨论任何基本/典型功能
选择自动获取 Typescript 检查您的
s
您可以防止您的拼写错误 组件通过打开
typedRoutes
配置选项。
这将生成一个 .d.ts
类型文件在 .next/types
,其中包含所有路线的输入信息,以便 Typescript 可以检查 href
值是有效的。
next.config.js✅ copied
const nextConfig = { experimental: { typedRoutes: true, }, } module.exports = nextConfig
有一些警告 – 最重要的是非文字字符串(例如动态创建的字符串,例如 /blog/${slug}
不会起作用,你必须施放它 as Route
)。
看 他们的文档以获取完整信息)
一些常见的 Google 第三方脚本的官方 NextJS 包
有一些使用 Google 第三方脚本的官方软件包。
所有官方第三方脚本均来自 Google – 并且可以通过添加 @next/third-parties/google
依赖性。
GoogleTagManager
将 Google 跟踪代码管理器容器实例化到您的页面GoogleAnalytics
设置 Google Analytics(在他们的文档中,但尚未在 npm 包中)GoogleMapsEmbed
延迟加载 Google 地图实例YouTubeEmbed
快速加载 Youtube 嵌入内容。 它使用 精简版 YouTube 嵌入 包裹。
用法示例:
app/layout.tsx✅ copied
import { YouTubeEmbed } from '@next/third-parties/google' export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}body> <YouTubeEmbed videoid="ogfYd705cRs" height={400} params="controls=0" /> html> ) }
NextJS 关于将 .env 放入存储库的建议
我对这个建议感到惊讶,尽管我可以理解他们的理由。 我有 绝不 见过 .env
致力于 git。 有时 .env.example
(虚拟值,您复制到 .env
) 或者 .env.test
(用于 CI 或在本地运行测试)。
这是他们文档的直接引用:
很高兴知道:.env、.env.development 和 .env.Production 文件应包含在您的存储库中,因为它们定义了默认值。 .env*.local 应添加到 .gitignore,因为这些文件将被忽略。 .env.local 是可以存储机密的地方。
轻松生成 sitemap.xml
如果你放一个 sitemap.ts
文件在你的 app
目录,您可以使用它生成文件数组以包含在 sitemap.xml
文件。
下面的示例显示了硬编码的站点地图项,但您可以将此函数转换为异步函数并从 API 或数据库动态加载。
app/sitemap.ts✅ copied
import { MetadataRoute } from 'next' export default function sitemap(): MetadataRoute.Sitemap { return [ { url: 'https://acme.com', lastModified: new Date(), changeFrequency: 'yearly', priority: 1, }, { url: 'https://acme.com/blog', lastModified: new Date(), changeFrequency: 'weekly', priority: 0.5, }, ] }
了解更多 查看他们的文档。
不稳定_缓存(应用程序路由器)
您可以使用 unstable_cache
缓存数据(例如数据库数据)并在多个查询之间使用缓存的数据。
顾名思义,这是不稳定的。 但根据我的经验,它的工作原理正如你所期望的那样。 NextJS 的未来版本有可能会改变它的工作方式(以及名称!)
someComponent.tsx✅ copied
import { getUser } from './data'; import { unstable_cache } from 'next/cache'; const getCachedUser = unstable_cache( async (id) => getUser(id), ['my-app-user'] ); export default async function Component({ userID }) { const user = await getCachedUser(userID); ... }
通过将 CI 配置为使用 .next/cache 来改进 CI 的构建缓存
接下来将使用 .next/cache
目录来保存一些可以在不同版本之间使用的缓存数据。
要在 CI 提供程序中使用此缓存,通常必须手动配置它。
这是通过 Vercel 自动完成的
这是一个例子 Github 操作,但是其他提供商的文档中有更多示例,例如 CircleCI、GitLab CI、BitBucket Pipelines 等。
build-workflow.yml✅ copied
uses: actions/cache@v3 with: path: | ~/.npm ${{ github.workspace }}/.next/cache key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} restore-keys: | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
查看 CI 文档在这里 获取完整信息。
获取用户的地理位置(如果您使用 Vercel)
接下来将使用 NextRequest
传递请求对象时的对象。 如果您使用过路由处理程序,您可能会熟悉它:
export async function GET(request) { // request param is the NextRequest return Response.json('some-response') }
在 Vercel 上,您会自动将地理位置添加到请求中。
request.geo.city request.geo.country request.geo.region request.geo.latitude request.geo.longitude
如果您使用 Vercel 进行部署,则可以使用以下命令访问 IP request.ip
, 或者 request.headers.get('X-Forwarded-For')
在大多数其他服务器上。
轻松删除 JSX 属性(例如 data-test
)
您可以轻松删除 JSX 属性 和 babel-plugin-react-remove-properties
。 您可能希望使用以下属性来执行此操作 data-testid="SomeTestId"
。
如果您使用 Babel,那么设置起来很容易。
next.config.js✅ copied
module.exports = { compiler: { reactRemoveProperties: true, // by default it will remove data-testid // but you can set `properties` to define a regex }, }
Next 的动态加载器
NextJS 有 next/dynamic
,这是两者的组合 React.lazy()
(让您推迟加载组件的代码,直到第一次渲染为止)和
(让您显示回退,直到其子级完成加载)。
以下是如何使用它的示例:
'use client' import { useState } from 'react' import dynamic from 'next/dynamic' // Client Components: const ComponentA = dynamic(() => import('../components/A')) const ComponentB = dynamic(() => import('../components/B')) const ComponentC = dynamic(() => import('../components/C'), { ssr: false }) export default function ClientComponentExample() { const [showMore, setShowMore] = useState(false) return ( <div> {/* Load immediately, but in a separate client bundle */} <ComponentA /> {/* Load on demand, only when/if the condition is met */} {showMore && <ComponentB />} <button onClick={() => setShowMore(!showMore)}>Togglebutton> {/* Load only on the client side */} <ComponentC /> div> ) }
一个相关的功能是您还可以根据需要加载其他脚本/文件。 这是一个例子:
export default function Page() { return ( <button onClick={async () => { const Fuse = (await import('fuse.js')).default const fuse = new Fuse(someData) console.log(fuse.search(value)) }} /> ) }
了解快速刷新(热重载)的工作原理
快速刷新是指在编辑文件时几乎立即重新加载组件(不会丢失状态)。
这 文档 更详细地介绍它,但我想指出一些有关快速刷新的重要事项。
-
“如果您编辑一个文件 仅导出 React 组件,快速刷新将仅更新该文件的代码,并重新渲染您的组件。 您可以编辑该文件中的任何内容,包括样式、渲染逻辑、事件处理程序或效果。”
-
“如果你 编辑包含非 React 组件导出的文件,快速刷新将重新运行该文件,以及导入它的其他文件。 因此,如果 Button.js 和 Modal.js 都导入 theme.js,则编辑 theme.js 将更新这两个组件。”
-
“最后, 如果您编辑由 React 树外部的文件导入的文件,快速刷新将回退到完全重新加载。 您可能有一个文件,它呈现 React 组件,但也导出由非 React 组件导入的值。 例如,也许您的组件还导出一个常量,并且非 React 实用程序文件导入它。 在这种情况下,请考虑将常量迁移到单独的文件并将其导入到两个文件中。 这将重新启用快速刷新功能。 其他案件通常可以用类似的方式解决。”
有时也可能不需要快速刷新。 为此,您可以设置 // @refresh reset
在您正在处理的文件中强制完全重新加载。
instrumentation.ts
如果你有一个名为 instrumention.ts
在根目录中(或在 src
),它将运行一个名为的导出函数 register
当 NextJS 服务器最初启动时。
您可以使用它来设置/运行必须初始化才能运行应用程序的外部代码。
这是一个例子:
import { init } from 'package-init' export function register() { init() }
输入检查您的 next.config.js 文件
您的文件扩展名必须为 .js
为您 next.config.js
文件,因此您无法获得完整的 Typescript 类型检查。 但是,如果添加以下行,那么您的 IDE 可能会运行一些类型检查:
next.config.js✅ copied
// @ts-check const nextConfig = { /* config options here */ } module.exports = nextConfig
中间件的 waitUntil
和 NextFetchEvent
(应用程序路由器)
如果您正在使用中间件并希望在后台运行一些代码,那么您可以使用 waitUntil
。 这是一个例子:
middlware.ts✅ copied
import { NextResponse } from 'next/server' import type { NextFetchEvent, NextRequest } from 'next/server' export function middleware(req: NextRequest, event: NextFetchEvent) { event.waitUntil( fetch('https://my-analytics-platform.com', { method: 'POST', body: JSON.stringify({ pathname: req.nextUrl.pathname }), }) ) return NextResponse.next() }
多行并引用 .env 文件中的其他变量
对于本地开发环境,可以快速轻松地使用您的 .env
设置一些变量。 但有时您会需要多行值。 这可以通过您的 .env
文件,像这样:
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- ... Kh9NV... ... -----END DSA PRIVATE KEY-----" PRIVATE_KEY_2="-----BEGIN RSA PRIVATE KEY-----nKh9NV...n-----END DSA PRIVATE KEY-----n"
您还可以像这样引用其他变量:
TWITTER_USER=nextjs TWITTER_URL=https://twitter.com/$TWITTER_USER
在上面的示例中,TWITTER_URL 将设置为“https://twitter.com/nextjs”
航线段控制
如果您使用的是应用程序路由器,则从您的应用程序路由器中导出一些值 page.tsx
, layout.tsx
或者 route.ts
可以配置该路由。
如果您使用过应用程序路由器,这可能是众所周知的,但我认为看到您可以配置的所有导出值很有趣。
export const dynamic = 'auto' export const dynamicParams = true export const revalidate = false export const fetchCache = 'auto' export const runtime = 'nodejs' export const preferredRegion = 'auto' export const maxDuration = 5 export default function MyComponent() {}
顺便说一句,这些值必须是静态的 – 它们是在运行时之前提取的。 所以, export const maxDuration = 5
会起作用,但是 export const maxDuration = 2.5 * 2
将不会。
对于每个配置的文档,我建议 读这个。
您可能想要设置一些元数据,例如:
您可以轻松地在服务器组件上完成此操作 生成视口()。
export function generateViewport({ params }) { return { themeColor: 'black', width: 'device-width', initialScale: 1, maximumScale: 1, colorScheme: 'dark', } /* name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> */ }
使用 isBot
检查请求是否来自已知的机器人,等等
如果你导入 userAgent
从 next/server
,它可用于告诉您传入请求的用户代理。 您可以用它做一些有趣的事情:
- 这
isBot
返回的布尔属性将告诉您它是否是已知的爬虫/机器人 - 这
device
布尔值以了解用户代理正在运行的设备(例如移动电话、台式机)。 它包括以下属性model
(设备型号),type
(控制台/手机/平板电脑/等),以及vendor
。
middleware.ts✅ copied
import { NextRequest, NextResponse, userAgent } from 'next/server' export function middleware(request: NextRequest) { const { isBot } = userAgent(request) if(isBot) { // do something for these bot users } return NextResponse.rewrite(request.nextUrl) }
next.config.js 阶段
在你的 next.config.js
文件中,您有时可能希望为不同的上下文返回不同的配置(构建/服务器/开发/测试。
这显示了如何使用它:
next.config.js✅ copied
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants') module.exports = (phase, { defaultConfig }) => { /* possible values: PHASE_EXPORT = 'phase-export' PHASE_PRODUCTION_BUILD = 'phase-production-build' PHASE_PRODUCTION_SERVER = 'phase-production-server' PHASE_DEVELOPMENT_SERVER = 'phase-development-server' PHASE_TEST = 'phase-test' */ if (phase === PHASE_DEVELOPMENT_SERVER) { return { /* development only config options here */ } } return { /* config options for all phases except development here */ } }
构建时获取更多调试信息
如果你跑 next build --debug
从你的命令行它将在调试模式下构建。
你也可以运行 next build --profile
以配置文件模式运行。
next info
将打印出本地平台信息,例如操作系统和架构。
全局错误页面(应用程序路由器)
我们大多数人都知道 layout.ts
, 而且当然 page.tsx
。 还有一些其他特殊文件名,包括 error.ts
和 global-error.ts
。
- 根
app/error.ts
border 不会捕获根 app/layout.js 或 app/template.js 组件中抛出的错误。 - 但你可以使用
app/global-error.ts
抓住这些。 它包装了整个应用程序。 - 因为它包裹了整个应用程序 – 包括布局文件 – 你必须包括
和
在
global-error.ts
。
在这里阅读更多内容 (和 这里 用于页面路由器的错误处理)。
使用应用程序路由器时,您应该使用元数据 API 设置元标签或标题标签(不要只是将它们添加到 你自己)。
import { Metadata } from 'next' // either Static metadata export const metadata: Metadata = { title: '...', } // or Dynamic metadata export async function generateMetadata({ params }) { return { title: '...', } }
注意:页面路由器也存在类似的文件,该文件更常用 – pages/_error.js
组件有一些布尔选项
我们都知道 组件,导入自
'next/link'
。
这是您通常使用它的方式:
import Link from 'next/link' export default function YourPage() { return <Link href="/home"> Go home Link> }
以及 href
属性,还有一些布尔属性:
替换 NextJS Link 组件上的布尔选项
首先是 replace
。 默认为 replace={false}
所以你只需要在将其设置为 true 时设置它。
当 true 时,next/link 将替换当前历史记录状态,而不是将新 URL 添加到浏览器历史记录中
import Link from 'next/link' export default function YourPageReplace() { return ( <Link href="/home" replace> Go home (replace) Link> ) }
你也可以这样做 useRouter()
: router.replace('/home')
。
链接组件上的滚动选项
这是另一个默认为 false 的布尔值,因此只有在将其设置为 true 时才需要设置它。
您可能已经注意到,当您单击 NextJS 中的链接时,它会滚动到页面顶部(以使其看起来像典型的 当它从头开始加载整个页面时)。
有时您可能想阻止这种行为并保持在当前滚动位置。 您可以使用 scroll
布尔值支持 :
page-scroll.tsx✅ copied
import Link from 'next/link' export default function Page() { return ( <Link href="/dashboard" scroll> Dashboard (scroll) Link> ) }
使用时也可以关闭自动滚动 useRouter()
的 push()
功能: router.push('/dash', { scroll: false })
请注意,您仍然可以在 URL 中使用锚点,如下所示:
<Link href="/dashboard#profile">ProfileLink>
预取
当。。。的时候 prefetch
布尔值已打开(默认情况下),链接的 href
URL 将在后台预取。
对于当前视口中的链接或在页面上滚动后可见的链接,将会发生此预取。
page-no-prefetch.tsx✅ copied
import Link from 'next/link' export default function Page() { return ( <> <Link href="/page1" prefetch={false}> Dashboard (will not be prefetched) Link> <Link href="/page2"> Dashboard (default behaviour - with prefetch enabled) Link> > ) }
预取也可以通过以下方式完成 useRouter()
, 例如 router.prefetch('/page2')
配置浏览器列表以针对旧版浏览器进行填充
NextJS 将为最新的浏览器自动填充(并在需要时自动添加 CSS 前缀) autoprefixer
,但是您可以通过添加配置来专门覆盖它将填充的版本 package.json
:
package.json✅ copied
{ "browserslist": [ "chrome 64", "edge 79", "firefox 67", "opera 51", "safari 12" ] }
您不必运行标准的 Next 服务器
如果您对应用程序有复杂的要求并且想要使用 NextJS,您可能会从设置自定义 Next 服务器中受益。
例如,您可以有一个 Express 应用程序,它通过标准 NextJS 渲染逻辑运行某些路由,但通过其自己的逻辑运行其他路由。
这是一个例子
const { createServer } = require('http') const { parse } = require('url') const next = require('next') const dev = process.env.NODE_ENV !== 'production' const hostname = 'localhost' const port = 3000 // when using middleware `hostname` and `port` must be provided below const app = next({ dev, hostname, port }) const handle = app.getRequestHandler() app.prepare().then(() => { createServer(async (req, res) => { // Be sure to pass `true` as the second argument to `url.parse`. // This tells it to parse the query portion of the URL. const parsedUrl = parse(req.url, true) const { pathname, query } = parsedUrl if (pathname === '/a') { await app.render(req, res, '/a', query) } else { await handle(req, res, parsedUrl) } }) .listen(port, () => { console.log(`> Ready on http://${hostname}:${port}`) }) })
这方面没有大量的文档,但是 这个堆栈溢出 解释 getRequestHandler 函数。
有一些很好的例子来说明如何使用它 在 Github 上。
全局错误页面(应用程序路由器)
我们大多数人都知道 layout.ts
, 而且当然 page.tsx
。 还有一些其他特殊文件名,包括 error.ts
和 global-error.ts
。
- 根
app/error.ts
border 不会捕获根 app/layout.js 或 app/template.js 组件中抛出的错误。 - 但你可以使用
app/global-error.ts
抓住这些。 它包装了整个应用程序。 - 因为它包裹了整个应用程序 – 包括布局文件 – 你必须包括
和
在
global-error.ts
。
在这里阅读更多内容 (和 这里 用于页面路由器的错误处理)。
使用应用程序路由器时,您应该使用元数据 API 设置元标签或标题标签(不要只是将它们添加到 你自己)。
import { Metadata } from 'next' // either Static metadata export const metadata: Metadata = { title: '...', } // or Dynamic metadata export async function generateMetadata({ params }) { return { title: '...', } }
注意:页面路由器也存在类似的文件,该文件更常用 – pages/_error.js
多区域(部署多个 Next.JS 应用程序)
如果您有一个大型应用程序(或现有应用程序),您可能希望将它们拆分为完全独立的应用程序并拥有自己的部署。
一个例子是 http://yoursite.com/blog
这是它自己的博客应用程序,然后该域上的其他所有内容都作为不同的应用程序。
您可以在您的 next.config.js
在您的主应用程序中,进行重写。 如果您有 2 个应用程序 home
和 blog
, 和 home
作为主要应用程序:
next.config.js (in main app)✅ copied
const { BLOG_URL } = process.env // e.g. http://localhost:9999/ module.exports = { async rewrites() { return [ { source: '/:path*', destination: `/:path*`, }, { source: '/blog', destination: `${BLOG_URL}/blog`, }, { source: '/blog/:path*', destination: `${BLOG_URL}/blog/:path*`, }, ] }, }
另一个的 next.config.js (blog
)应用程序只需要设置一个 basePath: '/blog
。
然后,当您运行主应用程序(已重写)时,您将在 localhost:9999
,但是博客应用程序上 localhost:9999
。 当您访问时 http://localhost:3000/blog/hi
它将被代理来自 http://localhost:9999/hi
。
因此,访问您网站的人不会知道部署了两个单独的应用程序。 当您为站点提供服务时,这需要更多配置,但如果您使用 Vercel,则设置起来很容易。
查看 这里有一个演示应用程序。
从 getServerSideProps() 返回 404 或重定向(页面路由器)
这不完全是隐藏功能或任何东西,但我看到一些应用程序在组件中显示 404(而不是在调用中) getServerSideProps()
)。 返回 404 状态很容易:
pages/somePage.tsx✅ copied
export async function getServerSideProps(context) { const yourData = await getYourData() if (!yourData.isSuccess) { return { notFound: true } } // otherwise continue as normal, // send yourData as props to component: return { props: { yourData } } }
您还可以返回一些其他特殊属性,例如强制重定向:
pages/somePage.tsx (getServerSideProps)✅ copied
// ... if(yourData.shouldRedirect) { return { redirect: { destination: '/', permanent: false, }, } } // ...
如果您正在使用 getStaticProps
然后你也可以以类似的方式强制 404 或重定向。 看看他们的 关于 getStaticProps 的文档在这里。