NotesNotes
首页
GitHub
首页
GitHub
  • Project Structure

Project Structure

Folder and file conversions

Top-level folders

appApp Router
pagesPages Router
publicStatic assets to be served
srcOptional application source folder

Top-level files

Next.js
next.config.jsConfiguration file for Next.js
package.jsonProject dependencies and scripts
instrumentation.tsOpenTelemetry and Instrumentation file
middleware.tsNext.js request middleware
.envEnvironment variables
.env.localLocal environment variables
.env.productionProduction environment variables
.env.developmentDevelopment environment variables
.eslintrc.jsonConfiguration file for ESLint
.gitignoreGit files and folders to ignore
next-env.d.tsTypeScript declaration file for Next.js
tsconfig.jsonConfiguration file for TypeScript
jsconfig.jsonConfiguration file for JavaScript

Routing Files

layout.js .jsx .tsxLayout
page.js .jsx .tsxPage
loading.js .jsx .tsxLoading UI
not-found.js .jsx .tsxNot found UI
error.js .jsx .tsxError UI
global-error.js .jsx .tsxGlobal error UI
route.js .tsAPI endpoint
template.js .jsx .tsxRe-rendered layout
default.js .jsx .tsxParallel route fallback page

Nested routes

folderRoute segment
folder/folderNested route segment

Dynamic routes

[folder]Dynamic route segment
[...folder]Catch-all route segment
[[...folder]]Optional catch-all route segment

Route Groups and private folders

(folder)Group routes without affecting routing
_folderOpt folder and all child segments out of routing

Route Groups can be nested.

Example

  • Organize routes without affecting the URL path

    image-20250620171433724

    Even though routes inside (marketing) and (shop) share the same URL hierarchy, you can create a different layout for each group by adding a layout.js file inside their folders.

    image-20250620171456302

Parallel and Intercepted Routes

@folderNamed slot
(.)folderIntercept same level
(..)folderIntercept one level above
(..)(..)folderIntercept two levels above
(...)folderIntercept from root

Metadata file conventions

App icons

favicon.icoFavicon file
icon.ico .jpg .jpeg .png .svgApp Icon file
icon.js .ts .tsxGenerated App Icon
apple-icon.jpg .jpeg, .pngApple App Icon file
apple-icon.js .ts .tsxGenerated Apple App Icon

SEO

sitemap.xmlSitemap file
sitemap.js .tsGenerated Sitemap
robots.txtRobots file
robots.js .tsGenerated Robots file

Organizing your project

Component hierarchy

The components defined in special files are rendered in a specific hierarchy:

  • layout.js
  • template.js
  • error.js (React error boundary)
  • loading.js (React suspense boundary)
  • not-found.js (React error boundary)
  • page.js or nested layout.js

image-20250620115540425

The components of a route segment will be nested inside the components of its parent segment.

image-20250620115619147

Colocation

A route is not publicly accessible until a page.js or route.js file is added to a route segment.

image-20250620115715915

And, even when a route is made publicly accessible, only the content returned by page.js or route.js is sent to the client.

image-20250620115803639

Private folders

Private folders can be created by prefixing a folder with an underscore: _folderName

his indicates the folder is a private implementation detail and should not be considered by the routing system, thereby opting the folder and all its subfolders out of routing.

image-20250620135923483

Route groups

Route groups can be created by wrapping a folder in parenthesis: (folderName)

This indicates the folder is for organizational purposes and should not be included in the route's URL path.

image-20250620140531898

Examples
  • Creating multiple root layouts

    To create multiple root layouts, remove the top-level layout.js file, and add a layout.js file inside each route group. This is useful for partitioning an application into sections that have a completely different UI or experience. The <html> and <body> tags need to be added to each root layout.

    image-20250620150130806

    In the example above, both (marketing) and (shop) have their own root layout.

Layouts and Pages

Creating a page

A page is UI that is rendered on a specific route.

image-20250623105910942

Creating a layout

A layout is UI that is shared between multiple pages.

// app/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {/* Layout UI */}
        {/* Place children where you want to render a page or nested layout */}
        <main>{children}</main>
      </body>
    </html>
  )
}

The layout above is called a root layout because it's defined at the root of the app directory. The root layout is required and must contain html and body tags.

Creating a nested route

A nested route is a route composed of multiple URL segments. For example, the /blog/[slug] route is composed of three segments:

  • / (Root Segment)
  • blog (Segment)
  • [slug] (Leaf Segment)

In Next.js:

  • Folders are used to define the route segments that map to URL segments.
  • Files (like page and layout) are used to create UI that is shown for a segment.

Nesting layouts

For example, to create a layout for the /blog route, add a new layout file inside the blog folder.

image-20250623150452533

Creating a dynamic segment

To create a dynamic segment, wrap the segment (folder) name in square brackets: [segmentName]. For example, in the app/blog/[slug]/page.tsx route, the [slug] is the dynamic segment.

export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await getPost(slug)
 
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  )
}

Dynamic Segments are passed as the params prop to layout, page, route, and generateMetadata functions.

the params prop is a promise. In version 14 and earlier, params was a synchronous prop.

Linking and Navigating

Server Rendering

There are two types of server rendering, based on when it happens:

  • Static Rendering (or Prerendering) happens at build time or during revalidation and the result is cached.
  • Dynamic Rendering happens at request time in response to a client request.

Prefetching

Prefetching is the process of loading a route in the background before the user navigates to it. This makes navigation between routes in your application feel instant, because by the time a user clicks on a link, the data to render the next route is already available client side.

Next.js automatically prefetches routes linked with the <Link> when they enter the user's viewport.

Streaming

To use streaming, create a loading.tsx in your route folder:

image-20250624112802944

What can make transitions slow?

Slow networks

To improve perceived performance, you can use the useLinkStatus hook to show inline visual feedback to the user (like spinners or text glimmers on the link) while a transition is in progress.

'use client'
 
import { useLinkStatus } from 'next/link'
 
export default function LoadingIndicator() {
  const { pending } = useLinkStatus()
  return pending ? (
    <div role="status" aria-label="Loading" className="spinner" />
  ) : null
}

useLinkStatus must be used within a descendant component of a Link component

Server and Client Components

By default, layouts and pages are Server Components, which lets you fetch data and render parts of your UI on the server, optionally cache the result, and stream it to the client. When you need interactivity or browser APIs, you can use Client Components to layer in functionality.

When to use Server and Client Components

Use Client Components when you need:

  • State and event handlers. E.g. onClick, onChange.
  • Lifecycle logic. E.g. useEffect.
  • Browser-only APIs. E.g. localStorage, window, Navigator.geolocation, etc.
  • Custom hooks.

Use Server Components when you need:

  • Fetch data from databases or APIs close to the source.
  • Use API keys, tokens, and other secrets without exposing them to the client.
  • Reduce the amount of JavaScript sent to the browser.
  • Improve the First Contentful Paint (FCP), and stream content progressively to the client.

Examples

Using Client Components

You can create a Client Component by adding the "use client" directive at the top of the file, above your imports.

Once a file is marked with "use client", all its imports and child components are considered part of the client bundle. This means you don't need to add the directive to every component that is intended for the client.

Client components cannot be async functions.

Passing data from Server to Client Components

You can pass data from Server Components to Client Components using props.

Interleaving Server and Client Components

You can pass Server Components as a prop to a Client Component. This allows you to visually nest server-rendered UI within Client components.

Context providers

React context is commonly used to share global state like the current theme. However, React context is not supported in Server Components.

To use context, create a Client Component that accepts children:

Third-party components

When using a third-party component that relies on client-only features, you can wrap it in a Client Component to ensure it works as expected.

If you try to use <Carousel /> directly within a Server Component, you'll see an error. This is because Next.js doesn't know <Carousel /> is using client-only features.

To fix this, you can wrap third-party components that rely on client-only features in your own Client Components:

'use client'
 
import { Carousel } from 'acme-carousel'
 
export default Carousel

Now, you can use <Carousel /> directly within a Server Component

Preventing environment poisoning

JavaScript modules can be shared between both Server and Client Components modules. This means it's possible to accidentally import server-only code into the client. For example, consider the following function:

export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
 
  return res.json()
}

This function contains an API_KEY that should never be exposed to the client.

In Next.js, only environment variables prefixed with NEXT_PUBLIC_ are included in the client bundle. If variables are not prefixed, Next.js replaces them with an empty string.

As a result, even though getData() can be imported and executed on the client, it won't work as expected.

To prevent accidental usage in Client Components, you can use the server-only package.

import 'server-only'
 
export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
 
  return res.json()
}

Now, if you try to import the module into a Client Component, there will be a build-time error.

Build Server Components

When runningnpm run buildin Next.js, it attempts to pre-render pages. At this time, the local server is not actually running, so fetching from http://localhost:3000 will fail. There are two solutions:

  1. Set the page to dynamic rendering, preventing pre-rendering at build time
  2. Set no-cache parameters when fetching: like [await fetch(${baseUrl}/api/users, { cache: "no-store" });]

Fetching data

Server Components

You can fetch data in Server Components using:

  1. The fetch API
  2. An ORM or database

Client Components

There are two ways to fetch data in Client Components, using:

  1. React's use hook
  2. A community library like SWR or React Query

Examples

Sequential data fetching

export default async function Page({ params }) {
  // These requests will be sequential
  const { username } = await params
  const artist = await getArtist(username)
  const albums = await getAlbums(username)
  return <div>{artist.name}</div>
}

Parallel data fetching

export default async function Page({
  params,
}: {
  params: Promise<{ username: string }>
}) {
  const { username } = await params
  const artistData = getArtist(username)
  const albumsData = getAlbums(username)
 
  // Initiate both requests in parallel
  const [artist, albums] = await Promise.all([artistData, albumsData])
 
  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums} />
    </>
  )
}

Good to know: If one request fails when using Promise.all, the entire operation will fail. To handle this, you can use the Promise.allSettled method instead.

Updating data

You can update data in Next.js using React's Server Functions.

What are Server Functions?

A Server Function is an asynchronous function that runs on the server. They can be called from client through a network request, which is why they must be asynchronous.

By convention, a Server Action is an async function used with startTransition. This happens automatically when the function is:

  • Passed to a <form> using the action prop.
  • Passed to a <button> using the formAction prop.

Creating Server Functions

A Server Function can be defined by using the use server directive. You can place the directive at the top of an asynchronous function to mark the function as a Server Function, or at the top of a separate file to mark all exports of that file.

export async function createPost(formData: FormData) {
  'use server'
  const title = formData.get('title')
  const content = formData.get('content')
 
  // Update data
  // Revalidate cache
}
 
export async function deletePost(formData: FormData) {
  'use server'
  const id = formData.get('id')
 
  // Update data
  // Revalidate cache
}

Server Components

Server Functions can be inlined in Server Components by adding the "use server" directive to the top of the function body:

export default function Page() {
  // Server Action
  async function createPost(formData: FormData) {
    'use server'
    // ...
  }
 
  return <></>
}

Client Components

It's not possible to define Server Functions in Client Components. However, you can invoke them in Client Components by importing them from a file that has the "use server" directive at the top of it:

'use server'
 
export async function createPost() {}

Passing actions as props

'use client'
 
export default function ClientComponent({
  updateItemAction,
}: {
  updateItemAction: (formData: FormData) => void
}) {
  return <form action={updateItemAction}>{/* ... */}</form>
}

Invoking Server Functions

There are two main ways you can invoke a Server Function:

  1. Forms in Server and Client Components
  2. Event Handlers and useEffect in Client Components

Forms

React extends the HTML <form> element to allow Server Function to be invoked with the HTML action prop.

When invoked in a form, the function automatically receives the FormData object. You can extract the data using the native FormData methods:

// app/ui/form.tsx
import { createPost } from '@/app/actions'
 
export function Form() {
  return (
    <form action={createPost}>
      <input type="text" name="title" />
      <input type="text" name="content" />
      <button type="submit">Create</button>
    </form>
  )
}
// app/actions.ts
'use server'
 
export async function createPost(formData: FormData) {
  const title = formData.get('title')
  const content = formData.get('content')
 
  // Update data
  // Revalidate cache
}

Event Handlers

You can invoke a Server Function in a Client Component by using event handlers such as onClick.

Caching and Revalidating

Caching is a technique for storing the result of data fetching and other computations so that future requests for the same data can be served faster, without doing the work again. While revalidation allows you to update cache entries without having to rebuild your entire application.

Next.js provides a few APIs to handle caching and revalidation. This guide will walk you through when and how to use them.

  • fetch
  • unstable_cache
  • revalidatePath
  • revalidateTag

fetch

export default async function Page() {
  const data = await fetch('https://...', { cache: 'force-cache' })
}

Good to know: Although fetch requests are not cached by default, Next.js will prerender routes that have fetch requests and cache the HTML. If you want to guarantee a route is dynamic, use the connection API.

To revalidate the data returned by a fetch request, you can use the next.revalidate option.

export default async function Page() {
  const data = await fetch('https://...', { next: { revalidate: 3600 } })
}

This will revalidate the data after a specified amount of seconds.

Next.js extends the Web fetch() API to allow each request on the server to set its own persistent caching and revalidation semantics.

unstable_cache

const getCachedUser = unstable_cache(
  async () => {
    return getUserById(userId)
  },
  [userId], // add the user ID to the cache key
  {
    tags: ['user'],
    revalidate: 3600,
  }
)

The function accepts a third optional object to define how the cache should be revalidated. It accepts:

  • tags: an array of tags used by Next.js to revalidate the cache.
  • revalidate: the number of seconds after cache should be revalidated.

revalidateTag

import { revalidateTag } from 'next/cache'
 
export async function updateUser(id: string) {
  // Mutate data
  revalidateTag('user')
}

You can reuse the same tag in multiple functions to revalidate them all at once. In other words, you can revalidate tags used in the options of the fetch and unstable_cache methods.

revalidatePath

revalidatePath is used to revalidate a route and following an event. To use it, call it in a Route Handler or Server Action:

// app/lib/actions.ts
import { revalidatePath } from 'next/cache'
 
export async function updateUser(id: string) {
  // Mutate data
  revalidatePath('/profile')
}

Error Handling

Handling expected errors

Expected errors are those that can occur during the normal operation of the application, such as those from server-side form validation or failed requests. These errors should be handled explicitly and returned to the client.

Handling uncaught exceptions

Uncaught exceptions are unexpected errors that indicate bugs or issues that should not occur during the normal flow of your application. These should be handled by throwing errors, which will then be caught by error boundaries.

Global errors

While less common, you can handle errors in the root layout using the global-error.js file, located in the root app directory, even when leveraging internationalization. Global error UI must define its own <html> and <body> tags, since it is replacing the root layout or template when active.

CSS

CSS Modules

CSS Modules locally scope CSS by generating unique class names. This allows you to use the same class in different files without worrying about naming collisions.

To start using CSS Modules, create a new file with the extension .module.css and import it into any component inside the app directory(it's not necessary).

Global CSS

You can use global CSS to apply styles across your application.

Create a app/global.css file and import it in the root layout to apply the styles to every route in your application

Image Optimization

The Next.js Image component extends the HTML <img> element to provide:

Local images

You can store static files, like images and fonts, under a folder called public in the root directory. Files inside public can then be referenced by your code starting from the base URL (/).

import Image from 'next/image'
 
export default function Page() {
  return (
    <Image
      src="/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

If the image is statically imported, Next.js will automatically determine the intrinsic width and height.

import Image from 'next/image'
import ProfileImage from './profile.png'
 
export default function Page() {
  return (
    <Image
      src={ProfileImage}
      alt="Picture of the author"
      // width={500} automatically provided
      // height={500} automatically provided
      // blurDataURL="data:..." automatically provided
      // placeholder="blur" // Optional blur-up while loading
    />
  )
}

Remote images

To use a remote image, you can provide a URL string for the src property.

Since Next.js does not have access to remote files during the build process, you'll need to provide the width, height and optional blurDataURL props manually. Alternatively, you can use the fill property to make the image fill the size of the parent element.

import Image from 'next/image'
 
export default function Page() {
  return (
    <Image
      src="https://s3.amazonaws.com/my-bucket/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

To safely allow images from remote servers, you need to define a list of supported URL patterns in next.config.js.

import type { NextConfig } from 'next'
 
const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 's3.amazonaws.com',
        port: '',
        pathname: '/my-bucket/**',
        search: '',
      },
    ],
  },
}
 
export default config

When using a remote image with the Image component by setting the image URL in the src property, the user's browser doesn't send a request directly to the URL, but instead sends it to the Next.js project server

Font Optimization

The next/font module automatically optimizes your fonts and removes external network requests for improved privacy and performance.

Google fonts

You can automatically self-host any Google Font. Fonts are included stored as static assets and served from the same domain as your deployment, meaning no requests are sent to Google by the browser when the user visits your site.

import { Geist } from 'next/font/google'
 
const geist = Geist({
  subsets: ['latin'],
})
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={geist.className}>
      <body>{children}</body>
    </html>
  )
}

Local fonts

To use a local font, import your font from next/font/local and specify the src of your local font file. Fonts can be stored in the public folder or co-located inside the app folder. For example:

import localFont from 'next/font/local'
 
const myFont = localFont({
  src: './my-font.woff2',
})
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={myFont.className}>
      <body>{children}</body>
    </html>
  )
}

The localFont function must be declared at the top of the module (in other words, at the top of the file), and its src value cannot be a path alias (e.g., @/fonts/my-font.woff2).

Metadata and OG images

The Metadata APIs can be used to define your application metadata for improved SEO and web shareability

Default fields

There are two default meta tags that are always added even if a route doesn't define metadata:

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

Static metadata

To define static metadata, export a Metadata object from a static layout.js or page.js file. For example, to add a title and description to the blog route:

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'My Blog',
  description: '...',
}
 
export default function Page() {}

Generated metadata

You can use generateMetadata function to fetch metadata that depends on data. For example, to fetch the title and description for a specific blog post:

import type { Metadata, ResolvingMetadata } from 'next'
 
type Props = {
  params: Promise<{ slug: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const slug = (await params).slug
 
  // fetch post information
  const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) =>
    res.json()
  )
 
  return {
    title: post.title,
    description: post.description,
  }
}
 
export default function Page({ params, searchParams }: Props) {}

Streaming metadata

For dynamically rendered pages, if resolving generateMetadata might block rendering, Next.js streams the resolved metadata separately and injects it into the HTML as soon as it's ready.

Statically rendered pages don’t use this behavior since metadata is resolved at build time.

Learn more about streaming metadata.

Memoizing data requests

There may be cases where you need to fetch the same data for metadata and the page itself. To avoid duplicate requests, you can use React's cache function to memoize the return value and only fetch the data once. For example, to fetch the blog post information for both the metadata and the page:

import { cache } from 'react'
import { db } from '@/app/lib/db'
 
// getPost will be used twice, but execute only once
export const getPost = cache(async (slug: string) => {
  const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
  return res
})

Static Open Graph images

Open Graph (OG) images are images that represent your site in social media. To add a static OG image to your application, create a opengraph-image.png file in the root of the app folder.

You can also add OG images for specific routes by creating a opengraph-image.png deeper down the folder structure.

Generated Open Graph images

The ImageResponse constructor allows you to generate dynamic images using JSX and CSS. This is useful for OG images that depend on data.

For example, to generate a unique OG image for each blog post, add a opengraph-image.ts file inside the blog folder, and import the ImageResponse constructor from next/og:

// app/blog/[slug]/opengraph-image.ts
import { ImageResponse } from 'next/og'
import { getPost } from '@/app/lib/data'
 
// Image metadata
export const size = {
  width: 1200,
  height: 630,
}
 
export const contentType = 'image/png'
 
// Image generation
export default async function Image({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
 
  return new ImageResponse(
    (
      // ImageResponse JSX element
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        {post.title}
      </div>
    )
  )
}

ImageResponse supports common CSS properties including flexbox and absolute positioning, custom fonts, text wrapping, centering, and nested images. See the full list of supported CSS properties.

Route Handlers and Middleware

Route Handlers

Route Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs.

image-20250703084912732

Convention

Route Handlers are defined in a route.js|ts file inside the app directory.

Route Handlers can be nested anywhere inside the app directory, similar to page.js and layout.js. But there cannot be a route.js file at the same route segment level as page.js.

Supported HTTP Methods

The following HTTP methods are supported: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. If an unsupported method is called, Next.js will return a 405 Method Not Allowed response.

Extended NextRequest and NextResponse APIs

In addition to supporting the native Request and Response APIs, Next.js extends them with NextRequest and NextResponse to provide convenient helpers for advanced use cases.

Caching

Route Handlers are not cached by default. You can, however, opt into caching for GET methods. Other supported HTTP methods are not cached. To cache a GET method, use a route config option such as export const dynamic = 'force-static' in your Route Handler file.

export const dynamic = 'force-static'
 
export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/...', {
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY,
    },
  })
  const data = await res.json()
 
  return Response.json({ data })
}

Good to know: Other supported HTTP methods are not cached, even if they are placed alongside a GET method that is cached, in the same file.

Special Route Handlers

Special Route Handlers like sitemap.ts, opengraph-image.tsx, and icon.tsx, and other metadata files remain static by default unless they use Dynamic APIs or dynamic config options.

Route Resolution

You can consider a route the lowest level routing primitive.

  • They do not participate in layouts or client-side navigations like page.
  • There cannot be a route.js file at the same route as page.js.

Each route.js or page.js file takes over all HTTP verbs for that route.

Middleware

Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.

Use cases

Some common scenarios where Middleware is effective include:

  • Quick redirects after reading parts of the incoming request
  • Rewriting to different pages based on A/B tests or experiments
  • Modifying headers for all pages or a subset of pages

Middleware is not a good fit for:

  • Slow data fetching
  • Session management

Using fetch with options.cache, options.next.revalidate, or options.next.tags, has no effect in Middleware.

Convention

Use the file middleware.ts (or .js) in the root of your project to define Middleware. For example, at the same level as pages or app, or inside src if applicable.

Note: While only one middleware.ts file is supported per project, you can still organize your middleware logic into modules. Break out middleware functionalities into separate .ts or .js files and import them into your main middleware.ts file. This allows for cleaner management of route-specific middleware, aggregated in the middleware.ts for centralized control. By enforcing a single middleware file, it simplifies configuration, prevents potential conflicts, and optimizes performance by avoiding multiple middleware layers.

Example

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}
 
// See "Matching Paths" below to learn more
export const config = {
  // matcher 告诉中间件只在这些路径上运行
  // `*` 表示匹配单个路径段
  // `+` 表示匹配一个或多个路径段
  // `?` 表示匹配零个或一个路径段
  // `:path*` 表示匹配所有后续路径
  matcher: '/about/:path*',
}

Read more about using middleware, or refer to the middleware API reference.

Last Updated: 7/17/25, 3:33 AM
Contributors: RuanCong, Leite