next 16.2.9 → 16.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-commit +1 -1
- package/dist/bin/next +2 -2
- package/dist/bin/next.map +0 -0
- package/dist/build/index.js +3 -3
- package/dist/build/swc/index.js +1 -1
- package/dist/build/turbopack-analyze/index.js +1 -1
- package/dist/build/turbopack-build/impl.js +1 -1
- package/dist/build/webpack-config.js +3 -3
- package/dist/bundle-analyzer/404.html +2 -2
- package/dist/bundle-analyzer/__next.__PAGE__.txt +1 -1
- package/dist/bundle-analyzer/__next._full.txt +1 -1
- package/dist/bundle-analyzer/__next._head.txt +1 -1
- package/dist/bundle-analyzer/__next._index.txt +1 -1
- package/dist/bundle-analyzer/__next._tree.txt +1 -1
- package/dist/bundle-analyzer/_not-found/__next._full.txt +1 -1
- package/dist/bundle-analyzer/_not-found/__next._head.txt +1 -1
- package/dist/bundle-analyzer/_not-found/__next._index.txt +1 -1
- package/dist/bundle-analyzer/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/bundle-analyzer/_not-found/__next._not-found.txt +1 -1
- package/dist/bundle-analyzer/_not-found/__next._tree.txt +1 -1
- package/dist/bundle-analyzer/_not-found.html +2 -2
- package/dist/bundle-analyzer/_not-found.txt +1 -1
- package/dist/bundle-analyzer/index.html +2 -2
- package/dist/bundle-analyzer/index.txt +1 -1
- package/dist/client/app-bootstrap.js +1 -1
- package/dist/client/index.js +1 -1
- package/dist/compiled/next-server/pages-api-turbo.runtime.dev.js +1 -1
- package/dist/compiled/next-server/pages-api-turbo.runtime.dev.js.map +1 -1
- package/dist/compiled/next-server/pages-api-turbo.runtime.prod.js +1 -1
- package/dist/compiled/next-server/pages-api-turbo.runtime.prod.js.map +1 -1
- package/dist/compiled/next-server/pages-api.runtime.dev.js +1 -1
- package/dist/compiled/next-server/pages-api.runtime.dev.js.map +1 -1
- package/dist/compiled/next-server/pages-turbo.runtime.dev.js +1 -1
- package/dist/compiled/next-server/pages-turbo.runtime.dev.js.map +1 -1
- package/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/dist/compiled/next-server/pages-turbo.runtime.prod.js.map +1 -1
- package/dist/compiled/next-server/pages.runtime.dev.js +1 -1
- package/dist/compiled/next-server/pages.runtime.dev.js.map +1 -1
- package/dist/compiled/next-server/server.runtime.prod.js +1 -1
- package/dist/compiled/next-server/server.runtime.prod.js.map +1 -1
- package/dist/docs/01-app/01-getting-started/02-project-structure.md +1 -1
- package/dist/docs/01-app/01-getting-started/03-layouts-and-pages.md +6 -4
- package/dist/docs/01-app/01-getting-started/04-linking-and-navigating.md +1 -1
- package/dist/docs/01-app/01-getting-started/05-server-and-client-components.md +1 -1
- package/dist/docs/01-app/01-getting-started/07-mutating-data.md +4 -3
- package/dist/docs/01-app/01-getting-started/09-revalidating.md +9 -8
- package/dist/docs/01-app/01-getting-started/13-fonts.md +1 -1
- package/dist/docs/01-app/01-getting-started/14-metadata-and-og-images.md +23 -9
- package/dist/docs/01-app/01-getting-started/17-deploying.md +1 -1
- package/dist/docs/01-app/02-guides/authentication.md +5 -5
- package/dist/docs/01-app/02-guides/backend-for-frontend.md +20 -8
- package/dist/docs/01-app/02-guides/cdn-caching.md +1 -1
- package/dist/docs/01-app/02-guides/custom-server.md +1 -1
- package/dist/docs/01-app/02-guides/data-security.md +19 -12
- package/dist/docs/01-app/02-guides/draft-mode.md +168 -105
- package/dist/docs/01-app/02-guides/forms.md +1 -1
- package/dist/docs/01-app/02-guides/how-revalidation-works.md +2 -2
- package/dist/docs/01-app/02-guides/mdx.md +1 -0
- package/dist/docs/01-app/02-guides/migrating-to-cache-components.md +533 -3
- package/dist/docs/01-app/02-guides/multi-zones.md +1 -1
- package/dist/docs/01-app/02-guides/package-bundling.md +1 -1
- package/dist/docs/01-app/02-guides/prefetching.md +5 -5
- package/dist/docs/01-app/02-guides/preventing-flash-before-hydration.md +3 -3
- package/dist/docs/01-app/02-guides/public-static-pages.md +1 -1
- package/dist/docs/01-app/02-guides/scripts.md +1 -1
- package/dist/docs/01-app/02-guides/server-actions.md +180 -0
- package/dist/docs/01-app/02-guides/streaming.md +0 -1
- package/dist/docs/01-app/02-guides/third-party-libraries.md +1 -0
- package/dist/docs/01-app/02-guides/upgrading/version-15.md +1 -1
- package/dist/docs/01-app/03-api-reference/01-directives/use-server.md +2 -0
- package/dist/docs/01-app/03-api-reference/03-file-conventions/dynamic-routes.md +4 -2
- package/dist/docs/01-app/03-api-reference/03-file-conventions/instrumentation.md +18 -4
- package/dist/docs/01-app/03-api-reference/05-config/01-next-config-js/rewrites.md +8 -6
- package/dist/docs/01-app/03-api-reference/05-config/01-next-config-js/serverActions.md +2 -0
- package/dist/docs/01-app/03-api-reference/05-config/01-next-config-js/transpilePackages.md +15 -3
- package/dist/docs/01-app/03-api-reference/05-config/02-typescript.md +1 -1
- package/dist/docs/01-app/04-glossary.md +1 -1
- package/dist/docs/02-pages/02-guides/upgrading/version-12.md +1 -1
- package/dist/docs/04-community/01-contribution-guide.md +2 -2
- package/dist/docs/index.md +0 -2
- package/dist/esm/build/index.js +3 -3
- package/dist/esm/build/swc/index.js +1 -1
- package/dist/esm/build/turbopack-analyze/index.js +1 -1
- package/dist/esm/build/turbopack-build/impl.js +1 -1
- package/dist/esm/build/webpack-config.js +3 -3
- package/dist/esm/client/app-bootstrap.js +1 -1
- package/dist/esm/client/index.js +1 -1
- package/dist/esm/lib/patch-incorrect-lockfile.js +3 -3
- package/dist/esm/server/config.js +1 -1
- package/dist/esm/server/dev/hot-reloader-turbopack.js +2 -2
- package/dist/esm/server/dev/hot-reloader-webpack.js +1 -1
- package/dist/esm/server/lib/app-info-log.js +1 -1
- package/dist/esm/server/lib/start-server.js +1 -1
- package/dist/esm/shared/lib/errors/canary-only-config-error.js +1 -1
- package/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/dist/server/config.js +1 -1
- package/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/dist/server/lib/app-info-log.js +1 -1
- package/dist/server/lib/start-server.js +1 -1
- package/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/dist/telemetry/anonymous-meta.js +1 -1
- package/dist/telemetry/events/session-stopped.js +2 -2
- package/dist/telemetry/events/swc-load-failure.js +1 -1
- package/dist/telemetry/events/version.js +2 -2
- package/package.json +10 -252
- /package/dist/bundle-analyzer/_next/static/{ShlsHWUU3q2GhICNPxAIf → -wnBUGOTjuvnrsp1fJxyv}/_buildManifest.js +0 -0
- /package/dist/bundle-analyzer/_next/static/{ShlsHWUU3q2GhICNPxAIf → -wnBUGOTjuvnrsp1fJxyv}/_clientMiddlewareManifest.json +0 -0
- /package/dist/bundle-analyzer/_next/static/{ShlsHWUU3q2GhICNPxAIf → -wnBUGOTjuvnrsp1fJxyv}/_ssgManifest.js +0 -0
|
@@ -122,7 +122,7 @@ Use `@slot` for named slots rendered by a parent layout. Use intercept patterns
|
|
|
122
122
|
| [`favicon`](/docs/app/api-reference/file-conventions/metadata/app-icons#favicon) | `.ico` | Favicon file |
|
|
123
123
|
| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#icon) | `.ico` `.jpg` `.jpeg` `.png` `.svg` | App Icon file |
|
|
124
124
|
| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Generated App Icon |
|
|
125
|
-
| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#apple-icon) | `.jpg` `.jpeg
|
|
125
|
+
| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#apple-icon) | `.jpg` `.jpeg` `.png` | Apple App Icon file |
|
|
126
126
|
| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Generated Apple App Icon |
|
|
127
127
|
|
|
128
128
|
#### Open Graph and Twitter images
|
|
@@ -287,10 +287,11 @@ You can use the [`<Link>` component](/docs/app/api-reference/components/link) to
|
|
|
287
287
|
|
|
288
288
|
For example, to generate a list of blog posts, import `<Link>` from `next/link` and pass a `href` prop to the component:
|
|
289
289
|
|
|
290
|
-
```tsx filename="app/ui/post.tsx" highlight={1,
|
|
290
|
+
```tsx filename="app/ui/post.tsx" highlight={1,2,11} switcher
|
|
291
291
|
import Link from 'next/link'
|
|
292
|
+
import { getPosts } from '@/lib/posts'
|
|
292
293
|
|
|
293
|
-
export default async function
|
|
294
|
+
export default async function Posts() {
|
|
294
295
|
const posts = await getPosts()
|
|
295
296
|
|
|
296
297
|
return (
|
|
@@ -305,10 +306,11 @@ export default async function Post({ post }) {
|
|
|
305
306
|
}
|
|
306
307
|
```
|
|
307
308
|
|
|
308
|
-
```jsx filename="app/ui/post.js" highlight={1,
|
|
309
|
+
```jsx filename="app/ui/post.js" highlight={1,2,11} switcher
|
|
309
310
|
import Link from 'next/link'
|
|
311
|
+
import { getPosts } from '@/lib/posts'
|
|
310
312
|
|
|
311
|
-
export default async function
|
|
313
|
+
export default async function Posts() {
|
|
312
314
|
const posts = await getPosts()
|
|
313
315
|
|
|
314
316
|
return (
|
|
@@ -65,7 +65,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|
|
65
65
|
```jsx filename="app/layout.js" switcher
|
|
66
66
|
import Link from 'next/link'
|
|
67
67
|
|
|
68
|
-
export default function Layout() {
|
|
68
|
+
export default function Layout({ children }) {
|
|
69
69
|
return (
|
|
70
70
|
<html>
|
|
71
71
|
<body>
|
|
@@ -14,7 +14,7 @@ This page explains how Server and Client Components work in Next.js and when to
|
|
|
14
14
|
|
|
15
15
|
## When to use Server and Client Components?
|
|
16
16
|
|
|
17
|
-
The client and server environments have different capabilities. Server and Client
|
|
17
|
+
The client and server environments have different capabilities. Server and Client Components allow you to run logic in each environment depending on your use case.
|
|
18
18
|
|
|
19
19
|
Use **Client Components** when you need:
|
|
20
20
|
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
title: Mutating Data
|
|
3
3
|
description: Learn how to mutate data using Server Functions and Server Actions in Next.js.
|
|
4
4
|
related:
|
|
5
|
-
title:
|
|
6
|
-
description: Learn more about the
|
|
5
|
+
title: Next steps
|
|
6
|
+
description: Learn more about Server Actions and the APIs mentioned in this page.
|
|
7
7
|
links:
|
|
8
|
+
- app/guides/server-actions
|
|
8
9
|
- app/api-reference/functions/revalidatePath
|
|
9
10
|
- app/api-reference/functions/revalidateTag
|
|
10
11
|
- app/api-reference/functions/redirect
|
|
11
12
|
---
|
|
12
13
|
|
|
13
|
-
You can mutate data in Next.js using [React Server Functions](https://react.dev/reference/rsc/server-functions). This page will go through how you can [create](#creating-server-functions) and [invoke](#invoking-server-functions) Server Functions.
|
|
14
|
+
You can mutate data in Next.js using [React Server Functions](https://react.dev/reference/rsc/server-functions). This page will go through how you can [create](#creating-server-functions) and [invoke](#invoking-server-functions) Server Functions. For Next.js-specific behaviors (single-roundtrip response, sequential dispatch, security, deployment), see [Server Actions and Mutations](/docs/app/guides/server-actions).
|
|
14
15
|
|
|
15
16
|
## What are Server Functions?
|
|
16
17
|
|
|
@@ -35,14 +35,15 @@ export async function getProducts() {
|
|
|
35
35
|
|
|
36
36
|
`cacheLife` accepts a profile name or a custom configuration object:
|
|
37
37
|
|
|
38
|
-
| Profile | `stale` | `revalidate` | `expire`
|
|
39
|
-
| --------- | ------- | ------------ |
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
38
|
+
| Profile | `stale` | `revalidate` | `expire` |
|
|
39
|
+
| --------- | ------- | ------------ | -------- |
|
|
40
|
+
| `default` | 5m | 15m | never |
|
|
41
|
+
| `seconds` | 30s | 1s | 60s |
|
|
42
|
+
| `minutes` | 5m | 1m | 1h |
|
|
43
|
+
| `hours` | 5m | 1h | 1d |
|
|
44
|
+
| `days` | 5m | 1d | 1w |
|
|
45
|
+
| `weeks` | 5m | 1w | 30d |
|
|
46
|
+
| `max` | 5m | 30d | 1y |
|
|
46
47
|
|
|
47
48
|
For fine-grained control, pass an object:
|
|
48
49
|
|
|
@@ -93,7 +93,7 @@ export default function MyApp({ Component, pageProps }) {
|
|
|
93
93
|
|
|
94
94
|
## Google fonts
|
|
95
95
|
|
|
96
|
-
You can automatically self-host any Google Font. Fonts are included
|
|
96
|
+
You can automatically self-host any Google Font. Fonts are included 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.
|
|
97
97
|
|
|
98
98
|
To start using a Google Font, import your chosen font from `next/font/google`:
|
|
99
99
|
|
|
@@ -162,17 +162,23 @@ import { getPost } from '@/app/lib/data'
|
|
|
162
162
|
export async function generateMetadata({
|
|
163
163
|
params,
|
|
164
164
|
}: {
|
|
165
|
-
params: { slug: string }
|
|
165
|
+
params: Promise<{ slug: string }>
|
|
166
166
|
}) {
|
|
167
|
-
const
|
|
167
|
+
const { slug } = await params
|
|
168
|
+
const post = await getPost(slug)
|
|
168
169
|
return {
|
|
169
170
|
title: post.title,
|
|
170
171
|
description: post.description,
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
export default async function Page({
|
|
175
|
-
|
|
175
|
+
export default async function Page({
|
|
176
|
+
params,
|
|
177
|
+
}: {
|
|
178
|
+
params: Promise<{ slug: string }>
|
|
179
|
+
}) {
|
|
180
|
+
const { slug } = await params
|
|
181
|
+
const post = await getPost(slug)
|
|
176
182
|
return <div>{post.title}</div>
|
|
177
183
|
}
|
|
178
184
|
```
|
|
@@ -181,7 +187,8 @@ export default async function Page({ params }: { params: { slug: string } }) {
|
|
|
181
187
|
import { getPost } from '@/app/lib/data'
|
|
182
188
|
|
|
183
189
|
export async function generateMetadata({ params }) {
|
|
184
|
-
const
|
|
190
|
+
const { slug } = await params
|
|
191
|
+
const post = await getPost(slug)
|
|
185
192
|
return {
|
|
186
193
|
title: post.title,
|
|
187
194
|
description: post.description,
|
|
@@ -189,7 +196,8 @@ export async function generateMetadata({ params }) {
|
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
export default async function Page({ params }) {
|
|
192
|
-
const
|
|
199
|
+
const { slug } = await params
|
|
200
|
+
const post = await getPost(slug)
|
|
193
201
|
return <div>{post.title}</div>
|
|
194
202
|
}
|
|
195
203
|
```
|
|
@@ -264,8 +272,13 @@ export const size = {
|
|
|
264
272
|
export const contentType = 'image/png'
|
|
265
273
|
|
|
266
274
|
// Image generation
|
|
267
|
-
export default async function Image({
|
|
268
|
-
|
|
275
|
+
export default async function Image({
|
|
276
|
+
params,
|
|
277
|
+
}: {
|
|
278
|
+
params: Promise<{ slug: string }>
|
|
279
|
+
}) {
|
|
280
|
+
const { slug } = await params
|
|
281
|
+
const post = await getPost(slug)
|
|
269
282
|
|
|
270
283
|
return new ImageResponse(
|
|
271
284
|
(
|
|
@@ -302,7 +315,8 @@ export const contentType = 'image/png'
|
|
|
302
315
|
|
|
303
316
|
// Image generation
|
|
304
317
|
export default async function Image({ params }) {
|
|
305
|
-
const
|
|
318
|
+
const { slug } = await params
|
|
319
|
+
const post = await getPost(slug)
|
|
306
320
|
|
|
307
321
|
return new ImageResponse(
|
|
308
322
|
(
|
|
@@ -39,7 +39,7 @@ Node.js deployments support all Next.js features. Learn how to [configure them](
|
|
|
39
39
|
|
|
40
40
|
## Docker
|
|
41
41
|
|
|
42
|
-
Next.js can be deployed to any provider that supports [Docker](https://www.docker.com/) containers. This includes container orchestrators like Kubernetes or a cloud provider that runs Docker. For
|
|
42
|
+
Next.js can be deployed to any provider that supports [Docker](https://www.docker.com/) containers. This includes container orchestrators like Kubernetes or a cloud provider that runs Docker. For best practices on containerizing your app, refer to Docker's official [Next.js](https://docs.docker.com/guides/nextjs) and [React.js](https://docs.docker.com/guides/reactjs) guides.
|
|
43
43
|
|
|
44
44
|
Docker deployments support all Next.js features. Learn how to [configure them](/docs/app/guides/self-hosting) for your infrastructure.
|
|
45
45
|
|
|
@@ -749,16 +749,16 @@ import { cookies } from 'next/headers'
|
|
|
749
749
|
import { decrypt } from '@/app/lib/session'
|
|
750
750
|
|
|
751
751
|
export async function updateSession() {
|
|
752
|
-
const
|
|
752
|
+
const cookieStore = await cookies()
|
|
753
|
+
const session = cookieStore.get('session')?.value
|
|
753
754
|
const payload = await decrypt(session)
|
|
754
755
|
|
|
755
756
|
if (!session || !payload) {
|
|
756
757
|
return null
|
|
757
758
|
}
|
|
758
759
|
|
|
759
|
-
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
|
760
|
-
|
|
761
|
-
).set('session', session, {
|
|
760
|
+
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
|
761
|
+
cookieStore.set('session', session, {
|
|
762
762
|
httpOnly: true,
|
|
763
763
|
secure: true,
|
|
764
764
|
expires: expires,
|
|
@@ -1120,7 +1120,7 @@ While Proxy can be useful for initial checks, it should not be your only line of
|
|
|
1120
1120
|
|
|
1121
1121
|
> **Tips**:
|
|
1122
1122
|
>
|
|
1123
|
-
> - In Proxy, you can also read cookies using `req.cookies.get('session')
|
|
1123
|
+
> - In Proxy, you can also read cookies using `req.cookies.get('session')?.value`.
|
|
1124
1124
|
> - Proxy uses the Node.js runtime, check if your Auth library and session management library are compatible. You may need to use [Middleware](https://github.com/vercel/next.js/blob/v15.5.6/docs/01-app/03-api-reference/03-file-conventions/middleware.mdx) if your Auth library only supports [Edge Runtime](/docs/app/api-reference/edge)
|
|
1125
1125
|
> - You can use the `matcher` property in the Proxy to specify which routes Proxy should run on. Although, for auth, it's recommended Proxy runs on all routes.
|
|
1126
1126
|
|
|
@@ -351,9 +351,9 @@ export async function POST(request: Request) {
|
|
|
351
351
|
try {
|
|
352
352
|
const clonedRequest = request.clone()
|
|
353
353
|
|
|
354
|
-
await request.
|
|
355
|
-
await clonedRequest.
|
|
356
|
-
await request.
|
|
354
|
+
await request.text()
|
|
355
|
+
await clonedRequest.text()
|
|
356
|
+
await request.text() // Throws error
|
|
357
357
|
|
|
358
358
|
return new Response(null, { status: 204 })
|
|
359
359
|
} catch {
|
|
@@ -367,9 +367,9 @@ export async function POST(request) {
|
|
|
367
367
|
try {
|
|
368
368
|
const clonedRequest = request.clone()
|
|
369
369
|
|
|
370
|
-
await request.
|
|
371
|
-
await clonedRequest.
|
|
372
|
-
await request.
|
|
370
|
+
await request.text()
|
|
371
|
+
await clonedRequest.text()
|
|
372
|
+
await request.text() // Throws error
|
|
373
373
|
|
|
374
374
|
return new Response(null, { status: 204 })
|
|
375
375
|
} catch {
|
|
@@ -612,7 +612,13 @@ export async function GET(request: NextRequest) {
|
|
|
612
612
|
const token = request.nextUrl.searchParams.get('session_token')
|
|
613
613
|
const redirectUrl = request.nextUrl.searchParams.get('redirect_url')
|
|
614
614
|
|
|
615
|
-
const
|
|
615
|
+
const destination = new URL(redirectUrl ?? '/', request.url)
|
|
616
|
+
// Prevent open redirects: only allow same-origin destinations
|
|
617
|
+
if (destination.origin !== request.nextUrl.origin) {
|
|
618
|
+
return new Response('Invalid redirect', { status: 400 })
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const response = NextResponse.redirect(destination)
|
|
616
622
|
|
|
617
623
|
response.cookies.set({
|
|
618
624
|
value: token,
|
|
@@ -634,7 +640,13 @@ export async function GET(request) {
|
|
|
634
640
|
const token = request.nextUrl.searchParams.get('session_token')
|
|
635
641
|
const redirectUrl = request.nextUrl.searchParams.get('redirect_url')
|
|
636
642
|
|
|
637
|
-
const
|
|
643
|
+
const destination = new URL(redirectUrl ?? '/', request.url)
|
|
644
|
+
// Prevent open redirects: only allow same-origin destinations
|
|
645
|
+
if (destination.origin !== request.nextUrl.origin) {
|
|
646
|
+
return new Response('Invalid redirect', { status: 400 })
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const response = NextResponse.redirect(destination)
|
|
638
650
|
|
|
639
651
|
response.cookies.set({
|
|
640
652
|
value: token,
|
|
@@ -27,7 +27,7 @@ CDNs that respect `s-maxage` and `stale-while-revalidate` can cache static and I
|
|
|
27
27
|
|
|
28
28
|
### Static assets
|
|
29
29
|
|
|
30
|
-
Static assets (JavaScript, CSS, images, fonts) served from `/_next/static/` include content hashes in their filenames and have a 1 year `max-age` and `immutable` directive: `public,max-age=31536000,immutable`
|
|
30
|
+
Static assets (JavaScript, CSS, images, fonts) served from `/_next/static/` include content hashes in their filenames and have a 1 year `max-age` and `immutable` directive: `public, max-age=31536000, immutable`
|
|
31
31
|
|
|
32
32
|
You can use [`assetPrefix`](/docs/app/api-reference/config/next-config-js/assetPrefix) to serve static assets from a different domain or CDN origin.
|
|
33
33
|
|
|
@@ -10,7 +10,7 @@ Next.js includes its own server with `next start` by default. If you have an exi
|
|
|
10
10
|
|
|
11
11
|
> **Good to know**:
|
|
12
12
|
>
|
|
13
|
-
> - Before deciding to use a custom server, keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements.
|
|
13
|
+
> - Before deciding to use a custom server, keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements.
|
|
14
14
|
> - When using standalone output mode, it does not trace custom server files. This mode outputs a separate minimal `server.js` file, instead. These cannot be used together.
|
|
15
15
|
|
|
16
16
|
Take a look at the [following example](https://github.com/vercel/next.js/tree/canary/examples/custom-server) of a custom server:
|
|
@@ -9,6 +9,7 @@ related:
|
|
|
9
9
|
- app/guides/authentication
|
|
10
10
|
- app/guides/content-security-policy
|
|
11
11
|
- app/guides/forms
|
|
12
|
+
- app/guides/server-actions
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
[React Server Components](https://react.dev/reference/rsc/server-components) improve performance and simplify data fetching, but also shift where and how data is accessed, changing some of the traditional security assumptions for handling data in frontend apps.
|
|
@@ -33,7 +34,7 @@ You should follow a **Zero Trust** model when adopting Server Components in an e
|
|
|
33
34
|
import { cookies } from 'next/headers'
|
|
34
35
|
|
|
35
36
|
export default async function Page() {
|
|
36
|
-
const cookieStore = cookies()
|
|
37
|
+
const cookieStore = await cookies()
|
|
37
38
|
const token = cookieStore.get('AUTH_TOKEN')?.value
|
|
38
39
|
|
|
39
40
|
const res = await fetch('https://api.example.com/profile', {
|
|
@@ -73,7 +74,8 @@ import { cookies } from 'next/headers'
|
|
|
73
74
|
// Component to Server Component which minimizes risk of passing it to a Client
|
|
74
75
|
// Component.
|
|
75
76
|
export const getCurrentUser = cache(async () => {
|
|
76
|
-
const
|
|
77
|
+
const cookieStore = await cookies()
|
|
78
|
+
const token = cookieStore.get('AUTH_TOKEN')
|
|
77
79
|
const decodedToken = await decryptAndValidate(token)
|
|
78
80
|
// Don't include secret tokens or private information as public fields.
|
|
79
81
|
// Use classes to avoid accidentally passing the whole object to the client.
|
|
@@ -116,12 +118,13 @@ export async function getProfileDTO(slug: string) {
|
|
|
116
118
|
```
|
|
117
119
|
|
|
118
120
|
```tsx filename="app/page.tsx"
|
|
119
|
-
import {
|
|
121
|
+
import { getProfileDTO } from '../../data/user-dto'
|
|
120
122
|
|
|
121
|
-
export async function Page({ params
|
|
123
|
+
export default async function Page({ params }) {
|
|
124
|
+
const { slug } = await params
|
|
122
125
|
// This page can now safely pass around this profile knowing
|
|
123
126
|
// that it shouldn't contain anything sensitive.
|
|
124
|
-
const profile = await
|
|
127
|
+
const profile = await getProfileDTO(slug)
|
|
125
128
|
...
|
|
126
129
|
}
|
|
127
130
|
```
|
|
@@ -137,7 +140,8 @@ This approach, however, makes it easier to accidentally expose private data to t
|
|
|
137
140
|
```tsx filename="app/page.tsx"
|
|
138
141
|
import Profile from './components/profile.tsx'
|
|
139
142
|
|
|
140
|
-
export async function Page({ params
|
|
143
|
+
export default async function Page({ params }) {
|
|
144
|
+
const { slug } = await params
|
|
141
145
|
const [rows] = await sql`SELECT * FROM user WHERE slug = ${slug}`
|
|
142
146
|
const userData = rows[0]
|
|
143
147
|
// EXPOSED: This exposes all the fields in userData to the client because
|
|
@@ -184,10 +188,11 @@ import { getUser } from '../data/user'
|
|
|
184
188
|
import Profile from './ui/profile'
|
|
185
189
|
|
|
186
190
|
export default async function Page({
|
|
187
|
-
params
|
|
191
|
+
params,
|
|
188
192
|
}: {
|
|
189
|
-
params: { slug: string }
|
|
193
|
+
params: Promise<{ slug: string }>
|
|
190
194
|
}) {
|
|
195
|
+
const { slug } = await params
|
|
191
196
|
const publicProfile = await getUser(slug)
|
|
192
197
|
return <Profile user={publicProfile} />
|
|
193
198
|
}
|
|
@@ -307,7 +312,7 @@ You should always validate input from client, as they can be easily modified. Fo
|
|
|
307
312
|
```tsx filename="app/page.tsx"
|
|
308
313
|
// BAD: Trusting searchParams directly
|
|
309
314
|
export default async function Page({ searchParams }) {
|
|
310
|
-
const isAdmin = searchParams.
|
|
315
|
+
const isAdmin = (await searchParams).isAdmin
|
|
311
316
|
if (isAdmin === 'true') {
|
|
312
317
|
// Vulnerable: relies on untrusted client data
|
|
313
318
|
return <AdminPanel />
|
|
@@ -319,7 +324,8 @@ import { cookies } from 'next/headers'
|
|
|
319
324
|
import { verifyAdmin } from './auth'
|
|
320
325
|
|
|
321
326
|
export default async function Page() {
|
|
322
|
-
const
|
|
327
|
+
const cookieStore = await cookies()
|
|
328
|
+
const token = cookieStore.get('AUTH_TOKEN')
|
|
323
329
|
const isAdmin = await verifyAdmin(token)
|
|
324
330
|
|
|
325
331
|
if (isAdmin) {
|
|
@@ -565,8 +571,9 @@ Mutations (e.g. logging out users, updating databases, invalidating caches) shou
|
|
|
565
571
|
```tsx filename="app/page.tsx"
|
|
566
572
|
// BAD: Triggering a mutation during rendering
|
|
567
573
|
export default async function Page({ searchParams }) {
|
|
568
|
-
if (searchParams.
|
|
569
|
-
cookies()
|
|
574
|
+
if ((await searchParams).logout) {
|
|
575
|
+
const cookieStore = await cookies()
|
|
576
|
+
cookieStore.delete('AUTH_TOKEN')
|
|
570
577
|
}
|
|
571
578
|
|
|
572
579
|
return <UserProfile />
|