.
This commit is contained in:
+721
@@ -0,0 +1,721 @@
|
||||
---
|
||||
title: Codemods
|
||||
description: Use codemods to upgrade your Next.js codebase when new features are released.
|
||||
---
|
||||
|
||||
Codemods are transformations that run on your codebase programmatically. This allows a large number of changes to be programmatically applied without having to manually go through every file.
|
||||
|
||||
Next.js provides Codemod transformations to help upgrade your Next.js codebase when an API is updated or deprecated.
|
||||
|
||||
## Usage
|
||||
|
||||
In your terminal, navigate (`cd`) into your project's folder, then run:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod <transform> <path>
|
||||
```
|
||||
|
||||
Replacing `<transform>` and `<path>` with appropriate values.
|
||||
|
||||
- `transform` - name of transform
|
||||
- `path` - files or directory to transform
|
||||
- `--dry` Do a dry-run, no code will be edited
|
||||
- `--print` Prints the changed output for comparison
|
||||
|
||||
## Upgrade
|
||||
|
||||
Upgrades your Next.js application, automatically running codemods and updating Next.js, React, and React DOM.
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod upgrade [revision]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `revision` (optional): Specify the upgrade type (`patch`, `minor`, `major`), an NPM dist tag (e.g. `latest`, `canary`, `rc`), or an exact version (e.g. `15.0.0`). Defaults to `minor` for stable versions.
|
||||
- `--verbose`: Show more detailed output during the upgrade process.
|
||||
|
||||
For example:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Upgrade to the latest patch (e.g. 16.0.7 -> 16.0.8)
|
||||
npx @next/codemod upgrade patch
|
||||
|
||||
# Upgrade to the latest minor (e.g. 15.3.7 -> 15.4.8). This is the default.
|
||||
npx @next/codemod upgrade minor
|
||||
|
||||
# Upgrade to the latest major (e.g. 15.5.7 -> 16.0.7)
|
||||
npx @next/codemod upgrade major
|
||||
|
||||
# Upgrade to a specific version
|
||||
npx @next/codemod upgrade 16
|
||||
|
||||
# Upgrade to the canary release
|
||||
npx @next/codemod upgrade canary
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If the target version is the same as or lower than your current version, the command exits without making changes.
|
||||
> - During the upgrade, you may be prompted to choose which Next.js codemods to apply and run React 19 codemods if upgrading React.
|
||||
|
||||
## Codemods
|
||||
|
||||
### 16.0
|
||||
|
||||
#### Remove `experimental_ppr` Route Segment Config from App Router pages and layouts
|
||||
|
||||
##### `remove-experimental-ppr`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest remove-experimental-ppr .
|
||||
```
|
||||
|
||||
This codemod removes the `experimental_ppr` Route Segment Config from App Router pages and layouts.
|
||||
|
||||
```diff filename="app/page.tsx"
|
||||
- export const experimental_ppr = true;
|
||||
```
|
||||
|
||||
#### Remove `unstable_` prefix from stabilized API
|
||||
|
||||
##### `remove-unstable-prefix`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest remove-unstable-prefix .
|
||||
```
|
||||
|
||||
This codemod removes the `unstable_` prefix from stabilized API.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { unstable_cacheTag as cacheTag } from 'next/cache'
|
||||
|
||||
cacheTag()
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```ts
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
cacheTag()
|
||||
```
|
||||
|
||||
#### Migrate from deprecated `middleware` convention to `proxy`
|
||||
|
||||
##### `middleware-to-proxy`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest middleware-to-proxy .
|
||||
```
|
||||
|
||||
This codemod migrates projects from using the deprecated `middleware` convention to using the `proxy` convention. It:
|
||||
|
||||
- Renames `middleware.<extension>` to `proxy.<extension>` (e.g. `middleware.ts` to `proxy.ts`)
|
||||
- Renames named export `middleware` to `proxy`
|
||||
- Renames Next.js config property `experimental.middlewarePrefetch` to `experimental.proxyPrefetch`
|
||||
- Renames Next.js config property `experimental.middlewareClientMaxBodySize` to `experimental.proxyClientMaxBodySize`
|
||||
- Renames Next.js config property `experimental.externalMiddlewareRewritesResolve` to `experimental.externalProxyRewritesResolve`
|
||||
- Renames Next.js config property `skipMiddlewareUrlNormalize` to `skipProxyUrlNormalize`
|
||||
|
||||
For example:
|
||||
|
||||
```ts filename="middleware.ts"
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export function middleware() {
|
||||
return NextResponse.next()
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```ts filename="proxy.ts"
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export function proxy() {
|
||||
return NextResponse.next()
|
||||
}
|
||||
```
|
||||
|
||||
#### Migrate from `next lint` to ESLint CLI
|
||||
|
||||
##### `next-lint-to-eslint-cli`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@canary next-lint-to-eslint-cli .
|
||||
```
|
||||
|
||||
This codemod migrates projects from using `next lint` to using the ESLint CLI with your local ESLint config. It:
|
||||
|
||||
- Creates an `eslint.config.mjs` file with Next.js recommended configurations
|
||||
- Updates `package.json` scripts to use `eslint .` instead of `next lint`
|
||||
- Adds necessary ESLint dependencies to `package.json`
|
||||
- Preserves existing ESLint configurations when found
|
||||
|
||||
For example:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "next lint"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "eslint ."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And creates:
|
||||
|
||||
```js filename="eslint.config.mjs"
|
||||
import { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
})
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends('next/core-web-vitals', 'next/typescript'),
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'.next/**',
|
||||
'out/**',
|
||||
'build/**',
|
||||
'next-env.d.ts',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default eslintConfig
|
||||
```
|
||||
|
||||
### 15.0
|
||||
|
||||
#### Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`
|
||||
|
||||
##### `app-dir-runtime-config-experimental-edge`
|
||||
|
||||
> **Note**: This codemod is App Router specific.
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest app-dir-runtime-config-experimental-edge .
|
||||
```
|
||||
|
||||
This codemod transforms [Route Segment Config `runtime`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/runtime) value `experimental-edge` to `edge`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
export const runtime = 'experimental-edge'
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```ts
|
||||
export const runtime = 'edge'
|
||||
```
|
||||
|
||||
#### Migrate to async Dynamic APIs
|
||||
|
||||
APIs that opted into dynamic rendering that previously supported synchronous access are now asynchronous. You can read more about this breaking change in the [upgrade guide](/docs/app/guides/upgrading/version-15).
|
||||
|
||||
##### `next-async-request-api`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest next-async-request-api .
|
||||
```
|
||||
|
||||
This codemod will transform dynamic APIs (`cookies()`, `headers()` and `draftMode()` from `next/headers`) that are now asynchronous to be properly awaited or wrapped with `React.use()` if applicable.
|
||||
When an automatic migration isn't possible, the codemod will either add a typecast (if a TypeScript file) or a comment to inform the user that it needs to be manually reviewed & updated.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx
|
||||
import { cookies, headers } from 'next/headers'
|
||||
const token = cookies().get('token')
|
||||
|
||||
function useToken() {
|
||||
const token = cookies().get('token')
|
||||
return token
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const name = cookies().get('name')
|
||||
}
|
||||
|
||||
function getHeader() {
|
||||
return headers().get('x-foo')
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```tsx
|
||||
import { use } from 'react'
|
||||
import {
|
||||
cookies,
|
||||
headers,
|
||||
type UnsafeUnwrappedCookies,
|
||||
type UnsafeUnwrappedHeaders,
|
||||
} from 'next/headers'
|
||||
const token = (cookies() as unknown as UnsafeUnwrappedCookies).get('token')
|
||||
|
||||
function useToken() {
|
||||
const token = use(cookies()).get('token')
|
||||
return token
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const name = (await cookies()).get('name')
|
||||
}
|
||||
|
||||
function getHeader() {
|
||||
return (headers() as unknown as UnsafeUnwrappedHeaders).get('x-foo')
|
||||
}
|
||||
```
|
||||
|
||||
When we detect property access on the `params` or `searchParams` props in the page / route entries (`page.js`, `layout.js`, `route.js`, or `default.js`) or the `generateMetadata` / `generateViewport` APIs,
|
||||
it will attempt to transform the callsite from a sync to an async function, and await the property access. If it can't be made async (such as with a Client Component), it will use `React.use` to unwrap the promise .
|
||||
|
||||
For example:
|
||||
|
||||
```tsx
|
||||
// page.tsx
|
||||
export default function Page({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: { slug: string }
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) {
|
||||
const { value } = searchParams
|
||||
if (value === 'foo') {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const { slug } = params
|
||||
return {
|
||||
title: `My Page - ${slug}`,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```tsx
|
||||
// page.tsx
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug: string }>
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const searchParams = await props.searchParams
|
||||
const { value } = searchParams
|
||||
if (value === 'foo') {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const { slug } = params
|
||||
return {
|
||||
title: `My Page - ${slug}`,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:** When this codemod identifies a spot that might require manual intervention, but we aren't able to determine the exact fix, it will add a comment or typecast to the code to inform the user that it needs to be manually updated. These comments are prefixed with **@next/codemod**, and typecasts are prefixed with `UnsafeUnwrapped`.
|
||||
> Your build will error until these comments are explicitly removed. [Read more](/docs/messages/sync-dynamic-apis).
|
||||
|
||||
#### Replace `geo` and `ip` properties of `NextRequest` with `@vercel/functions`
|
||||
|
||||
##### `next-request-geo-ip`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest next-request-geo-ip .
|
||||
```
|
||||
|
||||
This codemod installs `@vercel/functions` and transforms `geo` and `ip` properties of `NextRequest` with corresponding `@vercel/functions` features.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function GET(req: NextRequest) {
|
||||
const { geo, ip } = req
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```ts
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { geolocation, ipAddress } from '@vercel/functions'
|
||||
|
||||
export function GET(req: NextRequest) {
|
||||
const geo = geolocation(req)
|
||||
const ip = ipAddress(req)
|
||||
}
|
||||
```
|
||||
|
||||
### 14.0
|
||||
|
||||
#### Migrate `ImageResponse` imports
|
||||
|
||||
##### `next-og-import`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest next-og-import .
|
||||
```
|
||||
|
||||
This codemod moves transforms imports from `next/server` to `next/og` for usage of [Dynamic OG Image Generation](/docs/app/getting-started/metadata-and-og-images#generated-open-graph-images).
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
import { ImageResponse } from 'next/server'
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```js
|
||||
import { ImageResponse } from 'next/og'
|
||||
```
|
||||
|
||||
#### Use `viewport` export
|
||||
|
||||
##### `metadata-to-viewport-export`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest metadata-to-viewport-export .
|
||||
```
|
||||
|
||||
This codemod migrates certain viewport metadata to `viewport` export.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
export const metadata = {
|
||||
title: 'My App',
|
||||
themeColor: 'dark',
|
||||
viewport: {
|
||||
width: 1,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```js
|
||||
export const metadata = {
|
||||
title: 'My App',
|
||||
}
|
||||
|
||||
export const viewport = {
|
||||
width: 1,
|
||||
themeColor: 'dark',
|
||||
}
|
||||
```
|
||||
|
||||
### 13.2
|
||||
|
||||
#### Use Built-in Font
|
||||
|
||||
##### `built-in-next-font`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest built-in-next-font .
|
||||
```
|
||||
|
||||
This codemod uninstalls the `@next/font` package and transforms `@next/font` imports into the built-in `next/font`.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
import { Inter } from '@next/font/google'
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```js
|
||||
import { Inter } from 'next/font/google'
|
||||
```
|
||||
|
||||
### 13.0
|
||||
|
||||
#### Rename Next Image Imports
|
||||
|
||||
##### `next-image-to-legacy-image`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest next-image-to-legacy-image .
|
||||
```
|
||||
|
||||
Safely renames `next/image` imports in existing Next.js 10, 11, or 12 applications to `next/legacy/image` in Next.js 13. Also renames `next/future/image` to `next/image`.
|
||||
|
||||
For example:
|
||||
|
||||
```jsx filename="pages/index.js"
|
||||
import Image1 from 'next/image'
|
||||
import Image2 from 'next/future/image'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<Image1 src="/test.jpg" width="200" height="300" />
|
||||
<Image2 src="/test.png" width="500" height="400" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```jsx filename="pages/index.js"
|
||||
// 'next/image' becomes 'next/legacy/image'
|
||||
import Image1 from 'next/legacy/image'
|
||||
// 'next/future/image' becomes 'next/image'
|
||||
import Image2 from 'next/image'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<Image1 src="/test.jpg" width="200" height="300" />
|
||||
<Image2 src="/test.png" width="500" height="400" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Migrate to the New Image Component
|
||||
|
||||
##### `next-image-experimental`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest next-image-experimental .
|
||||
```
|
||||
|
||||
Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props.
|
||||
|
||||
- Removes `layout` prop and adds `style`.
|
||||
- Removes `objectFit` prop and adds `style`.
|
||||
- Removes `objectPosition` prop and adds `style`.
|
||||
- Removes `lazyBoundary` prop.
|
||||
- Removes `lazyRoot` prop.
|
||||
|
||||
#### Remove `<a>` Tags From Link Components
|
||||
|
||||
##### `new-link`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@latest new-link .
|
||||
```
|
||||
|
||||
<AppOnly>
|
||||
|
||||
Remove `<a>` tags inside [Link Components](/docs/app/api-reference/components/link).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
Remove `<a>` tags inside [Link Components](/docs/pages/api-reference/components/link).
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
For example:
|
||||
|
||||
```jsx
|
||||
<Link href="/about">
|
||||
<a>About</a>
|
||||
</Link>
|
||||
// transforms into
|
||||
<Link href="/about">
|
||||
About
|
||||
</Link>
|
||||
|
||||
<Link href="/about">
|
||||
<a onClick={() => console.log('clicked')}>About</a>
|
||||
</Link>
|
||||
// transforms into
|
||||
<Link href="/about" onClick={() => console.log('clicked')}>
|
||||
About
|
||||
</Link>
|
||||
```
|
||||
|
||||
### 11
|
||||
|
||||
#### Migrate from CRA
|
||||
|
||||
##### `cra-to-next`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod cra-to-next
|
||||
```
|
||||
|
||||
Migrates a Create React App project to Next.js; creating a Pages Router and necessary config to match behavior. Client-side only rendering is leveraged initially to prevent breaking compatibility due to `window` usage during SSR and can be enabled seamlessly to allow the gradual adoption of Next.js specific features.
|
||||
|
||||
Please share any feedback related to this transform [in this discussion](https://github.com/vercel/next.js/discussions/25858).
|
||||
|
||||
### 10
|
||||
|
||||
#### Add React imports
|
||||
|
||||
##### `add-missing-react-import`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod add-missing-react-import
|
||||
```
|
||||
|
||||
Transforms files that do not import `React` to include the import in order for the new [React JSX transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) to work.
|
||||
|
||||
For example:
|
||||
|
||||
```jsx filename="my-component.js"
|
||||
export default class Home extends React.Component {
|
||||
render() {
|
||||
return <div>Hello World</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```jsx filename="my-component.js"
|
||||
import React from 'react'
|
||||
export default class Home extends React.Component {
|
||||
render() {
|
||||
return <div>Hello World</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9
|
||||
|
||||
#### Transform Anonymous Components into Named Components
|
||||
|
||||
##### `name-default-component`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod name-default-component
|
||||
```
|
||||
|
||||
**Versions 9 and above.**
|
||||
|
||||
Transforms anonymous components into named components to make sure they work with [Fast Refresh](https://nextjs.org/blog/next-9-4#fast-refresh).
|
||||
|
||||
For example:
|
||||
|
||||
```jsx filename="my-component.js"
|
||||
export default function () {
|
||||
return <div>Hello World</div>
|
||||
}
|
||||
```
|
||||
|
||||
Transforms into:
|
||||
|
||||
```jsx filename="my-component.js"
|
||||
export default function MyComponent() {
|
||||
return <div>Hello World</div>
|
||||
}
|
||||
```
|
||||
|
||||
The component will have a camel-cased name based on the name of the file, and it also works with arrow functions.
|
||||
|
||||
### 8
|
||||
|
||||
> **Note**: Built-in AMP support and this codemod have been removed in Next.js 16.
|
||||
|
||||
#### Transform AMP HOC into page config
|
||||
|
||||
##### `withamp-to-config`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod withamp-to-config
|
||||
```
|
||||
|
||||
Transforms the `withAmp` HOC into Next.js 9 page configuration.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
// Before
|
||||
import { withAmp } from 'next/amp'
|
||||
|
||||
function Home() {
|
||||
return <h1>My AMP Page</h1>
|
||||
}
|
||||
|
||||
export default withAmp(Home)
|
||||
```
|
||||
|
||||
```js
|
||||
// After
|
||||
export default function Home() {
|
||||
return <h1>My AMP Page</h1>
|
||||
}
|
||||
|
||||
export const config = {
|
||||
amp: true,
|
||||
}
|
||||
```
|
||||
|
||||
### 6
|
||||
|
||||
#### Use `withRouter`
|
||||
|
||||
##### `url-to-withrouter`
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod url-to-withrouter
|
||||
```
|
||||
|
||||
Transforms the deprecated automatically injected `url` property on top level pages to using `withRouter` and the `router` property it injects. Read more here: [https://nextjs.org/docs/messages/url-deprecated](/docs/messages/url-deprecated)
|
||||
|
||||
For example:
|
||||
|
||||
```js filename="From"
|
||||
import React from 'react'
|
||||
export default class extends React.Component {
|
||||
render() {
|
||||
const { pathname } = this.props.url
|
||||
return <div>Current pathname: {pathname}</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="To"
|
||||
import React from 'react'
|
||||
import { withRouter } from 'next/router'
|
||||
export default withRouter(
|
||||
class extends React.Component {
|
||||
render() {
|
||||
const { pathname } = this.props.router
|
||||
return <div>Current pathname: {pathname}</div>
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This is one case. All the cases that are transformed (and tested) can be found in the [`__testfixtures__` directory](https://github.com/vercel/next.js/tree/canary/packages/next-codemod/transforms/__testfixtures__/url-to-withrouter).
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Upgrade Guides
|
||||
nav_title: Upgrading
|
||||
description: Learn how to upgrade to the latest versions of Next.js.
|
||||
---
|
||||
|
||||
Learn how to upgrade to the latest versions of Next.js following the versions-specific guides:
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: How to upgrade to version 14
|
||||
nav_title: Version 14
|
||||
description: Upgrade your Next.js Application from Version 13 to 14.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
## Upgrading from 13 to 14
|
||||
|
||||
To update to Next.js version 14, run the following command using your preferred package manager:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npm i next@next-14 react@18 react-dom@18 && npm i eslint-config-next@next-14 -D
|
||||
```
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add next@next-14 react@18 react-dom@18 && yarn add eslint-config-next@next-14 -D
|
||||
```
|
||||
|
||||
```bash filename="Terminal"
|
||||
pnpm i next@next-14 react@18 react-dom@18 && pnpm i eslint-config-next@next-14 -D
|
||||
```
|
||||
|
||||
```bash filename="Terminal"
|
||||
bun add next@next-14 react@18 react-dom@18 && bun add eslint-config-next@next-14 -D
|
||||
```
|
||||
|
||||
> **Good to know:** If you are using TypeScript, ensure you also upgrade `@types/react` and `@types/react-dom` to their latest versions.
|
||||
|
||||
### v14 Summary
|
||||
|
||||
- The minimum Node.js version has been bumped from 16.14 to 18.17, since 16.x has reached end-of-life.
|
||||
- The `next export` command has been removed in favor of `output: 'export'` config. Please see the [docs](https://nextjs.org/docs/app/guides/static-exports) for more information.
|
||||
- The `next/server` import for `ImageResponse` was renamed to `next/og`. A [codemod is available](/docs/app/guides/upgrading/codemods#next-og-import) to safely and automatically rename your imports.
|
||||
- The `@next/font` package has been fully removed in favor of the built-in `next/font`. A [codemod is available](/docs/app/guides/upgrading/codemods#built-in-next-font) to safely and automatically rename your imports.
|
||||
- The WASM target for `next-swc` has been removed.
|
||||
+627
@@ -0,0 +1,627 @@
|
||||
---
|
||||
title: How to upgrade to version 15
|
||||
nav_title: Version 15
|
||||
description: Upgrade your Next.js Application from Version 14 to 15.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
## Upgrading from 14 to 15
|
||||
|
||||
To update to Next.js version 15, you can use the `upgrade` codemod:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm dlx @next/codemod@canary upgrade latest
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npx @next/codemod@canary upgrade latest
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn dlx @next/codemod@canary upgrade latest
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bunx @next/codemod@canary upgrade latest
|
||||
```
|
||||
|
||||
If you prefer to do it manually, ensure that you're installing the latest Next & React versions:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm add next@latest react@latest react-dom@latest eslint-config-next@latest
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npm install next@latest react@latest react-dom@latest eslint-config-next@latest
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn add next@latest react@latest react-dom@latest eslint-config-next@latest
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bun add next@latest react@latest react-dom@latest eslint-config-next@latest
|
||||
```
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - If you see a peer dependencies warning, you may need to update `react` and `react-dom` to the suggested versions, or use the `--force` or `--legacy-peer-deps` flag to ignore the warning. This won't be necessary once both Next.js 15 and React 19 are stable.
|
||||
|
||||
## React 19
|
||||
|
||||
- The minimum versions of `react` and `react-dom` is now 19.
|
||||
- `useFormState` has been replaced by `useActionState`. The `useFormState` hook is still available in React 19, but it is deprecated and will be removed in a future release. `useActionState` is recommended and includes additional properties like reading the `pending` state directly. [Learn more](https://react.dev/reference/react/useActionState).
|
||||
- `useFormStatus` now includes additional keys like `data`, `method`, and `action`. If you are not using React 19, only the `pending` key is available. [Learn more](https://react.dev/reference/react-dom/hooks/useFormStatus).
|
||||
- Read more in the [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide).
|
||||
|
||||
> **Good to know:** If you are using TypeScript, ensure you also upgrade `@types/react` and `@types/react-dom` to their latest versions.
|
||||
|
||||
## Async Request APIs (Breaking change)
|
||||
|
||||
Previously synchronous Request-time APIs that rely on request information are now **asynchronous**:
|
||||
|
||||
- [`cookies`](/docs/app/api-reference/functions/cookies)
|
||||
- [`headers`](/docs/app/api-reference/functions/headers)
|
||||
- [`draftMode`](/docs/app/api-reference/functions/draft-mode)
|
||||
- `params` in [`layout.js`](/docs/app/api-reference/file-conventions/layout), [`page.js`](/docs/app/api-reference/file-conventions/page), [`route.js`](/docs/app/api-reference/file-conventions/route), [`default.js`](/docs/app/api-reference/file-conventions/default), [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons), and [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons).
|
||||
- `searchParams` in [`page.js`](/docs/app/api-reference/file-conventions/page)
|
||||
|
||||
To ease the burden of migration, a [codemod is available](/docs/app/guides/upgrading/codemods#150) to automate the process and the APIs can temporarily be accessed synchronously.
|
||||
|
||||
### `cookies`
|
||||
|
||||
#### Recommended Async Usage
|
||||
|
||||
```tsx
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const cookieStore = cookies()
|
||||
const token = cookieStore.get('token')
|
||||
|
||||
// After
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('token')
|
||||
```
|
||||
|
||||
#### Temporary Synchronous Usage
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const cookieStore = cookies()
|
||||
const token = cookieStore.get('token')
|
||||
|
||||
// After
|
||||
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
|
||||
// will log a warning in dev
|
||||
const token = cookieStore.get('token')
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const cookieStore = cookies()
|
||||
const token = cookieStore.get('token')
|
||||
|
||||
// After
|
||||
const cookieStore = cookies()
|
||||
// will log a warning in dev
|
||||
const token = cookieStore.get('token')
|
||||
```
|
||||
|
||||
### `headers`
|
||||
|
||||
#### Recommended Async Usage
|
||||
|
||||
```tsx
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const headersList = headers()
|
||||
const userAgent = headersList.get('user-agent')
|
||||
|
||||
// After
|
||||
const headersList = await headers()
|
||||
const userAgent = headersList.get('user-agent')
|
||||
```
|
||||
|
||||
#### Temporary Synchronous Usage
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const headersList = headers()
|
||||
const userAgent = headersList.get('user-agent')
|
||||
|
||||
// After
|
||||
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
|
||||
// will log a warning in dev
|
||||
const userAgent = headersList.get('user-agent')
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const headersList = headers()
|
||||
const userAgent = headersList.get('user-agent')
|
||||
|
||||
// After
|
||||
const headersList = headers()
|
||||
// will log a warning in dev
|
||||
const userAgent = headersList.get('user-agent')
|
||||
```
|
||||
|
||||
### `draftMode`
|
||||
|
||||
#### Recommended Async Usage
|
||||
|
||||
```tsx
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const { isEnabled } = draftMode()
|
||||
|
||||
// After
|
||||
const { isEnabled } = await draftMode()
|
||||
```
|
||||
|
||||
#### Temporary Synchronous Usage
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const { isEnabled } = draftMode()
|
||||
|
||||
// After
|
||||
// will log a warning in dev
|
||||
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
// Before
|
||||
const { isEnabled } = draftMode()
|
||||
|
||||
// After
|
||||
// will log a warning in dev
|
||||
const { isEnabled } = draftMode()
|
||||
```
|
||||
|
||||
### `params` & `searchParams`
|
||||
|
||||
#### Asynchronous Layout
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
// Before
|
||||
type Params = { slug: string }
|
||||
|
||||
export function generateMetadata({ params }: { params: Params }) {
|
||||
const { slug } = params
|
||||
}
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Params
|
||||
}) {
|
||||
const { slug } = params
|
||||
}
|
||||
|
||||
// After
|
||||
type Params = Promise<{ slug: string }>
|
||||
|
||||
export async function generateMetadata({ params }: { params: Params }) {
|
||||
const { slug } = await params
|
||||
}
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Params
|
||||
}) {
|
||||
const { slug } = await params
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
// Before
|
||||
export function generateMetadata({ params }) {
|
||||
const { slug } = params
|
||||
}
|
||||
|
||||
export default async function Layout({ children, params }) {
|
||||
const { slug } = params
|
||||
}
|
||||
|
||||
// After
|
||||
export async function generateMetadata({ params }) {
|
||||
const { slug } = await params
|
||||
}
|
||||
|
||||
export default async function Layout({ children, params }) {
|
||||
const { slug } = await params
|
||||
}
|
||||
```
|
||||
|
||||
#### Synchronous Layout
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
// Before
|
||||
type Params = { slug: string }
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Params
|
||||
}) {
|
||||
const { slug } = params
|
||||
}
|
||||
|
||||
// After
|
||||
import { use } from 'react'
|
||||
|
||||
type Params = Promise<{ slug: string }>
|
||||
|
||||
export default function Layout(props: {
|
||||
children: React.ReactNode
|
||||
params: Params
|
||||
}) {
|
||||
const params = use(props.params)
|
||||
const slug = params.slug
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
// Before
|
||||
export default function Layout({ children, params }) {
|
||||
const { slug } = params
|
||||
}
|
||||
|
||||
// After
|
||||
import { use } from 'react'
|
||||
export default async function Layout(props) {
|
||||
const params = use(props.params)
|
||||
const slug = params.slug
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Asynchronous Page
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
// Before
|
||||
type Params = { slug: string }
|
||||
type SearchParams = { [key: string]: string | string[] | undefined }
|
||||
|
||||
export function generateMetadata({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Params
|
||||
searchParams: SearchParams
|
||||
}) {
|
||||
const { slug } = params
|
||||
const { query } = searchParams
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Params
|
||||
searchParams: SearchParams
|
||||
}) {
|
||||
const { slug } = params
|
||||
const { query } = searchParams
|
||||
}
|
||||
|
||||
// After
|
||||
type Params = Promise<{ slug: string }>
|
||||
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Params
|
||||
searchParams: SearchParams
|
||||
}) {
|
||||
const params = await props.params
|
||||
const searchParams = await props.searchParams
|
||||
const slug = params.slug
|
||||
const query = searchParams.query
|
||||
}
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Params
|
||||
searchParams: SearchParams
|
||||
}) {
|
||||
const params = await props.params
|
||||
const searchParams = await props.searchParams
|
||||
const slug = params.slug
|
||||
const query = searchParams.query
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
// Before
|
||||
export function generateMetadata({ params, searchParams }) {
|
||||
const { slug } = params
|
||||
const { query } = searchParams
|
||||
}
|
||||
|
||||
export default function Page({ params, searchParams }) {
|
||||
const { slug } = params
|
||||
const { query } = searchParams
|
||||
}
|
||||
|
||||
// After
|
||||
export async function generateMetadata(props) {
|
||||
const params = await props.params
|
||||
const searchParams = await props.searchParams
|
||||
const slug = params.slug
|
||||
const query = searchParams.query
|
||||
}
|
||||
|
||||
export async function Page(props) {
|
||||
const params = await props.params
|
||||
const searchParams = await props.searchParams
|
||||
const slug = params.slug
|
||||
const query = searchParams.query
|
||||
}
|
||||
```
|
||||
|
||||
#### Synchronous Page
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
// Before
|
||||
type Params = { slug: string }
|
||||
type SearchParams = { [key: string]: string | string[] | undefined }
|
||||
|
||||
export default function Page({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Params
|
||||
searchParams: SearchParams
|
||||
}) {
|
||||
const { slug } = params
|
||||
const { query } = searchParams
|
||||
}
|
||||
|
||||
// After
|
||||
import { use } from 'react'
|
||||
|
||||
type Params = Promise<{ slug: string }>
|
||||
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
|
||||
|
||||
export default function Page(props: {
|
||||
params: Params
|
||||
searchParams: SearchParams
|
||||
}) {
|
||||
const params = use(props.params)
|
||||
const searchParams = use(props.searchParams)
|
||||
const slug = params.slug
|
||||
const query = searchParams.query
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
// Before
|
||||
export default function Page({ params, searchParams }) {
|
||||
const { slug } = params
|
||||
const { query } = searchParams
|
||||
}
|
||||
|
||||
// After
|
||||
import { use } from "react"
|
||||
|
||||
export default function Page(props) {
|
||||
const params = use(props.params)
|
||||
const searchParams = use(props.searchParams)
|
||||
const slug = params.slug
|
||||
const query = searchParams.query
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Route Handlers
|
||||
|
||||
```tsx filename="app/api/route.ts" switcher
|
||||
// Before
|
||||
type Params = { slug: string }
|
||||
|
||||
export async function GET(request: Request, segmentData: { params: Params }) {
|
||||
const params = segmentData.params
|
||||
const slug = params.slug
|
||||
}
|
||||
|
||||
// After
|
||||
type Params = Promise<{ slug: string }>
|
||||
|
||||
export async function GET(request: Request, segmentData: { params: Params }) {
|
||||
const params = await segmentData.params
|
||||
const slug = params.slug
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
// Before
|
||||
export async function GET(request, segmentData) {
|
||||
const params = segmentData.params
|
||||
const slug = params.slug
|
||||
}
|
||||
|
||||
// After
|
||||
export async function GET(request, segmentData) {
|
||||
const params = await segmentData.params
|
||||
const slug = params.slug
|
||||
}
|
||||
```
|
||||
|
||||
<AppOnly>
|
||||
|
||||
## `runtime` configuration (Breaking change)
|
||||
|
||||
The `runtime` [segment configuration](/docs/app/api-reference/file-conventions/route-segment-config/runtime) previously supported a value of `experimental-edge` in addition to `edge`. Both configurations refer to the same thing, and to simplify the options, we will now error if `experimental-edge` is used. To fix this, update your `runtime` configuration to `edge`. A [codemod](/docs/app/guides/upgrading/codemods#app-dir-runtime-config-experimental-edge) is available to automatically do this.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
## `fetch` requests
|
||||
|
||||
[`fetch` requests](/docs/app/api-reference/functions/fetch) are no longer cached by default.
|
||||
|
||||
To opt specific `fetch` requests into caching, you can pass the `cache: 'force-cache'` option.
|
||||
|
||||
```js filename="app/layout.js"
|
||||
export default async function RootLayout() {
|
||||
const a = await fetch('https://...') // Not Cached
|
||||
const b = await fetch('https://...', { cache: 'force-cache' }) // Cached
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
To opt all `fetch` requests in a layout or page into caching, you can use the `export const fetchCache = 'default-cache'` [segment config option](/docs/app/api-reference/file-conventions/route-segment-config). If individual `fetch` requests specify a `cache` option, that will be used instead.
|
||||
|
||||
```js filename="app/layout.js"
|
||||
// Since this is the root layout, all fetch requests in the app
|
||||
// that don't set their own cache option will be cached.
|
||||
export const fetchCache = 'default-cache'
|
||||
|
||||
export default async function RootLayout() {
|
||||
const a = await fetch('https://...') // Cached
|
||||
const b = await fetch('https://...', { cache: 'no-store' }) // Not cached
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Route Handlers
|
||||
|
||||
`GET` functions in [Route Handlers](/docs/app/api-reference/file-conventions/route) are no longer cached by default. To opt `GET` methods into caching, you can use a [route config option](/docs/app/api-reference/file-conventions/route-segment-config) such as `export const dynamic = 'force-static'` in your Route Handler file.
|
||||
|
||||
```js filename="app/api/route.js"
|
||||
export const dynamic = 'force-static'
|
||||
|
||||
export async function GET() {}
|
||||
```
|
||||
|
||||
## Client Cache
|
||||
|
||||
When navigating between pages via `<Link>` or `useRouter`, [page](/docs/app/api-reference/file-conventions/page) segments are no longer reused from the [Client Cache](/docs/app/glossary#client-cache). However, they are still reused during browser backward and forward navigation and for shared layouts.
|
||||
|
||||
To opt page segments into caching, you can use the [`staleTimes`](/docs/app/api-reference/config/next-config-js/staleTimes) config option:
|
||||
|
||||
```js filename="next.config.js"
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
staleTimes: {
|
||||
dynamic: 30,
|
||||
static: 180,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
[Layouts](/docs/app/api-reference/file-conventions/layout) and [loading states](/docs/app/api-reference/file-conventions/loading) are still cached and reused on navigation.
|
||||
|
||||
## `next/font`
|
||||
|
||||
The `@next/font` package has been removed in favor of the built-in [`next/font`](/docs/app/api-reference/components/font). A [codemod is available](/docs/app/guides/upgrading/codemods#built-in-next-font) to safely and automatically rename your imports.
|
||||
|
||||
```js filename="app/layout.js"
|
||||
// Before
|
||||
import { Inter } from '@next/font/google'
|
||||
|
||||
// After
|
||||
import { Inter } from 'next/font/google'
|
||||
```
|
||||
|
||||
## bundlePagesRouterDependencies
|
||||
|
||||
`experimental.bundlePagesExternals` is now stable and renamed to `bundlePagesRouterDependencies`.
|
||||
|
||||
```js filename="next.config.js"
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Before
|
||||
experimental: {
|
||||
bundlePagesExternals: true,
|
||||
},
|
||||
|
||||
// After
|
||||
bundlePagesRouterDependencies: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
## serverExternalPackages
|
||||
|
||||
`experimental.serverComponentsExternalPackages` is now stable and renamed to `serverExternalPackages`.
|
||||
|
||||
```js filename="next.config.js"
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Before
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ['package-name'],
|
||||
},
|
||||
|
||||
// After
|
||||
serverExternalPackages: ['package-name'],
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
## Speed Insights
|
||||
|
||||
Auto instrumentation for Speed Insights was removed in Next.js 15.
|
||||
|
||||
To continue using Speed Insights, follow the [Vercel Speed Insights Quickstart](https://vercel.com/docs/speed-insights/quickstart) guide.
|
||||
|
||||
## `NextRequest` Geolocation
|
||||
|
||||
The `geo` and `ip` properties on `NextRequest` have been removed as these values are provided by your hosting provider. A [codemod](/docs/app/guides/upgrading/codemods#150) is available to automate this migration.
|
||||
|
||||
If you are using Vercel, you can alternatively use the `geolocation` and `ipAddress` functions from [`@vercel/functions`](https://vercel.com/docs/functions/vercel-functions-package) instead:
|
||||
|
||||
```ts filename="middleware.ts"
|
||||
import { geolocation } from '@vercel/functions'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const { city } = geolocation(request)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts filename="middleware.ts"
|
||||
import { ipAddress } from '@vercel/functions'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const ip = ipAddress(request)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
+1227
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user