From 8c32a0744adf53da85b0959853c4e76e9084917f Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 13:18:18 +0000 Subject: [PATCH 01/39] Re-order pages --- .../{03-forms-and-mutations.mdx => 02-forms-and-mutations.mdx} | 0 .../02-data-fetching/{02-patterns.mdx => 03-patterns.mdx} | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) rename docs/02-app/01-building-your-application/02-data-fetching/{03-forms-and-mutations.mdx => 02-forms-and-mutations.mdx} (100%) rename docs/02-app/01-building-your-application/02-data-fetching/{02-patterns.mdx => 03-patterns.mdx} (99%) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/03-forms-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-forms-and-mutations.mdx similarity index 100% rename from docs/02-app/01-building-your-application/02-data-fetching/03-forms-and-mutations.mdx rename to docs/02-app/01-building-your-application/02-data-fetching/02-forms-and-mutations.mdx diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-patterns.mdx b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx similarity index 99% rename from docs/02-app/01-building-your-application/02-data-fetching/02-patterns.mdx rename to docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx index 29a2dae007219..60b97d826cb17 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-patterns.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx @@ -1,5 +1,6 @@ --- -title: Data Fetching Patterns +title: Patterns and Best Practices +nav_title: Data Fetching Patterns and Best Practices description: Learn about common data fetching patterns in React and Next.js. --- From 09e30dc4ddd02c0e4af3df2de5658fb1608646ce Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 13:27:49 +0000 Subject: [PATCH 02/39] Rename form-and-mutations to server-actions-and-mutations - Server Actions can be used outside forms, we'd like to capture this by showing more examples of how Server Actions can be used --- ...-and-mutations.mdx => 02-server-actions-and-mutations.mdx} | 4 ++-- .../03-data-fetching/03-forms-and-mutations.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename docs/02-app/01-building-your-application/02-data-fetching/{02-forms-and-mutations.mdx => 02-server-actions-and-mutations.mdx} (99%) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-forms-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx similarity index 99% rename from docs/02-app/01-building-your-application/02-data-fetching/02-forms-and-mutations.mdx rename to docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index c38ef97adb29c..aa0c35b77389c 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-forms-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -1,6 +1,6 @@ --- -title: Forms and Mutations -nav_title: Forms and Mutations +title: Server Actions and Mutations +nav_title: Server Actions and Mutations description: Learn how to handle form submissions and data mutations with Next.js. --- diff --git a/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx b/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx index a0713acd69ae2..6b7fbd99f4ff8 100644 --- a/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx +++ b/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx @@ -2,7 +2,7 @@ title: Forms and Mutations nav_title: Forms and Mutations description: Learn how to handle form submissions and data mutations with Next.js. -source: app/building-your-application/data-fetching/forms-and-mutations +source: app/building-your-application/data-fetching/server-actions-and-mutations --- {/* DO NOT EDIT. The content of this doc is generated from the source above. To edit the content of this page, navigate to the source page in your editor. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */} From 202bcbe787415a2b56bf31e60620c4ea28e0717a Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 15:10:04 +0000 Subject: [PATCH 03/39] Restructure page --- .../02-server-actions-and-mutations.mdx | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index aa0c35b77389c..cb000a3b91f9b 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -6,25 +6,8 @@ description: Learn how to handle form submissions and data mutations with Next.j {/* The content of this doc is shared between the app and pages router. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */} - - -Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using **API Routes**. - -> **Good to know:** -> -> - We will soon recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router and using [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations#how-server-actions-work) for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route. -> - API Routes [do not specify CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS), meaning they are same-origin only by default. -> - Since API Routes run on the server, we're able to use sensitive values (like API keys) through [Environment Variables](/docs/pages/building-your-application/configuring/environment-variables) without exposing them to the client. This is critical for the security of your application. - - - -Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using **Server Actions**. - -
- Examples - - [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms)
@@ -54,9 +37,21 @@ View the examples below for [revalidating data from Server Actions](#revalidatin
-## Examples + -### Server-only Forms +Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using **API Routes**. + +> **Good to know:** +> +> - We will soon recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router and using [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations#how-server-actions-work) for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route. +> - API Routes [do not specify CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS), meaning they are same-origin only by default. +> - Since API Routes run on the server, we're able to use sensitive values (like API keys) through [Environment Variables](/docs/pages/building-your-application/configuring/environment-variables) without exposing them to the client. This is critical for the security of your application. + + + +### Examples + +#### Server-only Forms @@ -171,7 +166,7 @@ export default function Page() { > **Good to know**: `
` takes the [FormData](https://developer.mozilla.org/docs/Web/API/FormData/FormData) data type. In the example above, the FormData submitted via the HTML [`form`](https://developer.mozilla.org/docs/Web/HTML/Element/form) is accessible in the server action `create`. -### Revalidating Data +#### Revalidating Data Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-your-application/caching) on demand. You can invalidate an entire route segment with [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath): @@ -223,7 +218,7 @@ export default async function submit() { -### Redirecting +#### Redirecting @@ -282,7 +277,7 @@ export default async function submit() { -### Form Validation +#### Form Validation We recommend using HTML validation like `required` and `type="email"` for basic form validation. @@ -356,7 +351,7 @@ export default async function submit(formData) { -### Displaying Loading State +#### Displaying Loading State @@ -510,7 +505,7 @@ export default function Page() { -### Error Handling +#### Error Handling @@ -724,7 +719,7 @@ export default function Page() { -### Optimistic Updates +#### Optimistic Updates Use [`useOptimistic`](https://react.dev/reference/react/useOptimistic) to optimistically update the UI before the Server Action finishes, rather than waiting for the response: @@ -801,7 +796,7 @@ export function Thread({ messages }) { -### Setting Cookies +#### Setting Cookies @@ -856,7 +851,7 @@ export async function create() { -### Reading Cookies +#### Reading Cookies @@ -911,7 +906,7 @@ export async function read() { -### Deleting Cookies +#### Deleting Cookies @@ -967,3 +962,17 @@ export async function delete() { See [additional examples](/docs/app/api-reference/functions/cookies#deleting-cookies) for deleting cookies from Server Actions. + +## Calling a Server Action from outside a form + +### Examples + +#### `useEffect + +## Security + +### Using React's `taintObjectReference` API + +### Built-in CSRF Protection + +### Setting Encryption Keys From 47245678e2264481735c83ed3e1e6ca03e77ec90 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 16:16:10 +0000 Subject: [PATCH 04/39] Add "What are Server Actions" section --- .../02-data-fetching/02-server-actions-and-mutations.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index cb000a3b91f9b..bbeb356812317 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -10,7 +10,9 @@ description: Learn how to handle form submissions and data mutations with Next.j - [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms) - +## What are Server Actions? + +Server Actions are **asynchronous server functions** that can be called directly from your React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. ## How Server Actions Work From 3b4e09b0c96375ab16b86372988bda0f9ddfb2f6 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 16:25:38 +0000 Subject: [PATCH 05/39] Add "How Server Actions" work section --- .../02-server-actions-and-mutations.mdx | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index bbeb356812317..ef5ecb6bf404c 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -8,34 +8,19 @@ description: Learn how to handle form submissions and data mutations with Next.j -- [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms) - ## What are Server Actions? Server Actions are **asynchronous server functions** that can be called directly from your React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. -## How Server Actions Work - -With Server Actions, you don't need to manually create API endpoints. Instead, you define asynchronous server functions that can be called directly from your components. - -> **🎥 Watch:** Learn more about forms and mutations with the App Router → [YouTube (10 minutes)](https://youtu.be/dDpZfOQBMaU?si=cJZHlUu_jFhCzHUg). - -Server Actions can be defined in Server Components or called from Client Components. Defining the action in a Server Component allows the form to function without JavaScript, providing progressive enhancement. - -> **Good to know:** -> -> - Forms calling Server Actions from Server Components can function without JavaScript. -> - Forms calling Server Actions from Client Components will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. -> - Server Actions inherit the [runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes) from the page or layout they are used on. -> - Server Actions work with fully static routes (including revalidating data with ISR). +## How Server Actions work -## Revalidating Cached Data - -Server Actions integrate deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When a form is submitted, the Server Action can update cached data and revalidate any cache keys that should change. - -Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple actions per route. Further, the browser does not need to refresh on form submission. In a single network roundtrip, Next.js can return both the updated UI and the refreshed data. - -View the examples below for [revalidating data from Server Actions](#revalidating-data). +- Server Actions are a React feature that integrates deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When the action is called, Next.js can return both the updated UI and new data in a single round trip. +- Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. +- In Server Components, Server Actions can be used to handle forms without JavaScript, providing progressive enhancement. +- In Client Components, forms calling Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. +- Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple actions per route. The browser also does not refresh on form submission. +- Server Actions inherit the [runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes) from the page or layout they are used on. +- The return value of Server Actions must be serializable by React. See the React docs for a list of [serializable arguments and values](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). @@ -51,10 +36,14 @@ Forms enable you to create and update data in web applications. Next.js provides -### Examples +## Examples + +### Server-only Forms #### Server-only Forms +- [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms) + With the Pages Router, you need to manually create API endpoints to handle securely mutating data on the server. From fe4d1342209d0d87782fad520972847dbdbe7f9c Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 17:14:45 +0000 Subject: [PATCH 06/39] Explain "use server" and add RSC and RCC examples - Expand on how Server Actions can be inlined in Server Components, and how they can be called from Client Components. --- .../02-server-actions-and-mutations.mdx | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index ef5ecb6bf404c..abc01017e4e40 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -12,13 +12,16 @@ description: Learn how to handle form submissions and data mutations with Next.j Server Actions are **asynchronous server functions** that can be called directly from your React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. +> **🎥 Watch:** Learn more about forms and mutations with the App Router → [YouTube (10 minutes)](https://youtu.be/dDpZfOQBMaU?si=cJZHlUu_jFhCzHUg). + ## How Server Actions work -- Server Actions are a React feature that integrates deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When the action is called, Next.js can return both the updated UI and new data in a single round trip. -- Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. +- Server Actions are a React feature that integrates deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When an action is called, Next.js can return both the updated UI and new data in a single server round trip. +- Server Actions can be defined inline in Server Components or called from Client Components. - In Server Components, Server Actions can be used to handle forms without JavaScript, providing progressive enhancement. - In Client Components, forms calling Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. - Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple actions per route. The browser also does not refresh on form submission. +- Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. - Server Actions inherit the [runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes) from the page or layout they are used on. - The return value of Server Actions must be serializable by React. See the React docs for a list of [serializable arguments and values](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). @@ -38,6 +41,62 @@ Forms enable you to create and update data in web applications. Next.js provides ## Examples + + +A Server Action can be defined with the React [´"use server"`](https://react.dev/reference/react/use-server) directive. By adding `"use server"` directive, the function will execute on the server regardless of whether it's called from a Server or Client Component. + +In Server Components, the action can be inlined and the `"use server"` added at the top of the function body: + +```tsx filename="app/page.tsx" switcher +export default function Page() { + // Server Action + async function create(formData: FormData) { + 'use server' + + // ... + } + + return ( + // ... + ) +} +``` + +```jsx filename="app/page.jsx" switcher +export default function Page() { + // Server Action + async function create(formData) { + 'use server' + + // ... + } + + return ( + // ... + ) +} +``` + +To call a Server Action in a Client Component, create a new file and add the `"use server"` directive at the top of it. All functions within the file will be marked as Server Actions that can be reused in both Client and Server Components: + +```tsx filename="app/actions.ts" switcher +'use server' + +export async function create(formData: FormData) { + // ... +} +``` + +```js filename="app/actions.js" switcher +'use server' + +export async function create(formData) { + // ... +} +``` + + + ### Server-only Forms #### Server-only Forms From 687239ce761bb8f2c4d8055b4f612dbd4c0d59bc Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 17:19:28 +0000 Subject: [PATCH 07/39] Expand on the "Server-only forms" example --- .../02-server-actions-and-mutations.mdx | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index abc01017e4e40..f1962d44f7644 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -99,9 +99,59 @@ export async function create(formData) { ### Server-only Forms -#### Server-only Forms + + +React extends the HTML [``](https://developer.mozilla.org/docs/Web/HTML/Element/form) element to allow Server Actions to be called with the `action` prop. + +When called in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object containing the captured form data. You can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): + +```tsx filename="app/invoices/page.tsx" switcher +export default function Page() { + async function createInvoice(formData: FormData) { + 'use server' + + const rawFormData = { + customerId: formData.get('customerId'), + amount: formData.get('amount'), + status: formData.get('status'), + } + + // mutate data + // revalidate cache + } + + return ... +} +``` + +```jsx filename="app/invoices/page.jsx" switcher +export default function Page() { + async function createInvoice(formData) { + 'use server' + + const rawFormData = { + customerId: formData.get('customerId'), + amount: formData.get('amount'), + status: formData.get('status'), + } + + // mutate data + // revalidate cache + } + + return
...
+} +``` + +> **Good to know:** When working with forms that have many fields, you may want to consider using the [`entries()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries) method with JavaScript's [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries). For example: `const rawFormData = Object.fromEntries(formData.entries())` + +To learn more about the `"use server"` directive and forms in React, see the following resources: -- [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms) +- [React `
` documentation](https://react.dev/reference/react-dom/components/form#handle-form-submission-with-a-server-action) +- [React `use server` documentation](https://react.dev/reference/react/use-server) +- Next.js Example: [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms) + + @@ -186,36 +236,6 @@ export default function Page() { -To create a server-only form, define the Server Action in a Server Component. The action can either be defined inline with the `"use server"` directive at the top of the function, or in a separate file with the directive at the top of the file. - -```tsx filename="app/page.tsx" switcher -export default function Page() { - async function create(formData: FormData) { - 'use server' - - // mutate data - // revalidate cache - } - - return ... -} -``` - -```jsx filename="app/page.jsx" switcher -export default function Page() { - async function create(formData) { - 'use server' - - // mutate data - // revalidate cache - } - - return
...
-} -``` - -> **Good to know**: `
` takes the [FormData](https://developer.mozilla.org/docs/Web/API/FormData/FormData) data type. In the example above, the FormData submitted via the HTML [`form`](https://developer.mozilla.org/docs/Web/HTML/Element/form) is accessible in the server action `create`. - #### Revalidating Data Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-your-application/caching) on demand. You can invalidate an entire route segment with [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath): From 77c99e9ad8c1ec657dfa5012275391f61c599d45 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 17:51:26 +0000 Subject: [PATCH 08/39] Use try/catch in examples instead of 2nd function --- .../02-server-actions-and-mutations.mdx | 78 ++++++++++--------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index f1962d44f7644..c6f56f48bdd57 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -246,8 +246,13 @@ Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-yo import { revalidatePath } from 'next/cache' export default async function submit() { - await submitForm() - revalidatePath('/') + try { + // ... + } catch (error) { + // ... + } + + revalidatePath('/posts') } ``` @@ -257,8 +262,13 @@ export default async function submit() { import { revalidatePath } from 'next/cache' export default async function submit() { - await submitForm() - revalidatePath('/') + try { + // ... + } catch (error) { + // ... + } + + revalidatePath('/posts') } ``` @@ -270,7 +280,12 @@ Or invalidate a specific data fetch with a cache tag using [`revalidateTag`](/do import { revalidateTag } from 'next/cache' export default async function submit() { - await addPost() + try { + // ... + } catch (error) { + // ... + } + revalidateTag('posts') } ``` @@ -281,7 +296,11 @@ export default async function submit() { import { revalidateTag } from 'next/cache' export default async function submit() { - await addPost() + try { + // ... + } catch (error) { + // ... + } revalidateTag('posts') } ``` @@ -290,31 +309,6 @@ export default async function submit() { #### Redirecting - - -If you would like to redirect the user to a different route after a mutation, you can [`redirect`](/docs/pages/building-your-application/routing/api-routes#response-helpers) to any absolute or relative URL: - -```ts filename="pages/api/submit.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const id = await addPost() - res.redirect(307, `/post/${id}`) -} -``` - -```js filename="pages/api/submit.js" switcher -export default async function handler(req, res) { - const id = await addPost() - res.redirect(307, `/post/${id}`) -} -``` - - - If you would like to redirect the user to a different route after the completion of a Server Action, you can use [`redirect`](/docs/app/api-reference/functions/redirect) and any absolute or relative URL: @@ -325,10 +319,15 @@ If you would like to redirect the user to a different route after the completion import { redirect } from 'next/navigation' import { revalidateTag } from 'next/cache' -export default async function submit() { - const id = await addPost() +export default async function submit(id: string) { + try { + // ... + } catch (error) { + // ... + } + revalidateTag('posts') // Update cached posts - redirect(`/post/${id}`) // Navigate to new route + redirect(`/post/${id}`) // Navigate to the new post page } ``` @@ -338,10 +337,15 @@ export default async function submit() { import { redirect } from 'next/navigation' import { revalidateTag } from 'next/cache' -export default async function submit() { - const id = await addPost() +export default async function submit(id) { + try { + // ... + } catch (error) { + // ... + } + revalidateTag('posts') // Update cached posts - redirect(`/post/${id}`) // Navigate to new route + redirect(`/post/${id}`) // Navigate to the new post page } ``` From 5b38723eadf3828242df50fb144dd3d825c4ef56 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 17:52:08 +0000 Subject: [PATCH 09/39] Improve form validation example, use safeParse --- .../02-server-actions-and-mutations.mdx | 97 ++++++++++++++----- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index c6f56f48bdd57..1317b56d5279a 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -351,59 +351,60 @@ export default async function submit(id) { -#### Form Validation - -We recommend using HTML validation like `required` and `type="email"` for basic form validation. - -For more advanced server-side validation, use a schema validation library like [zod](https://zod.dev/) to validate the structure of the parsed form data: - +If you would like to redirect the user to a different route after a mutation, you can [`redirect`](/docs/pages/building-your-application/routing/api-routes#response-helpers) to any absolute or relative URL: + ```ts filename="pages/api/submit.ts" switcher import type { NextApiRequest, NextApiResponse } from 'next' -import { z } from 'zod' - -const schema = z.object({ - // ... -}) export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const parsed = schema.parse(req.body) - // ... + const id = await addPost() + res.redirect(307, `/post/${id}`) } ``` ```js filename="pages/api/submit.js" switcher -import { z } from 'zod' - -const schema = z.object({ - // ... -}) - export default async function handler(req, res) { - const parsed = schema.parse(req.body) - // ... + const id = await addPost() + res.redirect(307, `/post/${id}`) } ``` +#### Server-side Form Validation + +We recommend using HTML validation like `required` and `type="email"` for basic client-side form validation. + +For more advanced server-side validation, you can use a schema validation library like [zod](https://zod.dev/) to validate the form fields before mutating the data: + ```tsx filename="app/actions.ts" switcher import { z } from 'zod' const schema = z.object({ - // ... + id: z.string({ + invalid_type_error: 'Invalid ID', + }), }) export default async function submit(formData: FormData) { - const parsed = schema.parse({ + const validatedFields = schema.safeParse({ id: formData.get('id'), }) + + // Return early if the form data is invalid + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + } + } + // ... } ``` @@ -412,19 +413,65 @@ export default async function submit(formData: FormData) { import { z } from 'zod' const schema = z.object({ - // ... + id: z.string({ + invalid_type_error: 'Invalid ID', + }), }) export default async function submit(formData) { - const parsed = schema.parse({ + const validatedFields = schema.safeParse({ id: formData.get('id'), }) + + // Return early if the form data is invalid + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + } + } + // ... } ``` +Please refer to the [Zod documentation](https://zod.dev/) for more information. + + + +```ts filename="pages/api/submit.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const parsed = schema.parse(req.body) + // ... +} +``` + +```js filename="pages/api/submit.js" switcher +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function handler(req, res) { + const parsed = schema.parse(req.body) + // ... +} +``` + + + #### Displaying Loading State From 7ddb1a8f37975f62effb9dce3e33ce754784e8d5 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 18:00:36 +0000 Subject: [PATCH 10/39] Reorganize sections --- .../02-server-actions-and-mutations.mdx | 402 +++++++++--------- 1 file changed, 201 insertions(+), 201 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 1317b56d5279a..f9a6b218d8d8d 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -236,7 +236,7 @@ export default function Page() { -#### Revalidating Data +### Revalidating Data Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-your-application/caching) on demand. You can invalidate an entire route segment with [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath): @@ -307,7 +307,7 @@ export default async function submit() { -#### Redirecting +### Redirecting @@ -376,7 +376,7 @@ export default async function handler(req, res) { -#### Server-side Form Validation +### Server-side Form Validation We recommend using HTML validation like `required` and `type="email"` for basic client-side form validation. @@ -472,165 +472,11 @@ export default async function handler(req, res) { -#### Displaying Loading State +### Error Handling -Use the [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a loading state when a form is submitting on the server. The `useFormStatus` hook can only be used as a child of a `form` element using a Server Action. - -For example, the following submit button: - -```tsx filename="app/submit-button.tsx" switcher -'use client' - -import { useFormStatus } from 'react-dom' - -export function SubmitButton() { - const { pending } = useFormStatus() - - return ( - - ) -} -``` - -```jsx filename="app/submit-button.jsx" switcher -'use client' - -import { useFormStatus } from 'react-dom' - -export function SubmitButton() { - const { pending } = useFormStatus() - - return ( - - ) -} -``` - -`` can then be used in a form with a Server Action: - -```tsx filename="app/page.tsx" switcher -import { SubmitButton } from '@/app/submit-button' - -export default async function Home() { - return ( - - - - - ) -} -``` - -```jsx filename="app/page.jsx" switcher -import { SubmitButton } from '@/app/submit-button' - -export default async function Home() { - return ( -
- - - - ) -} -``` - -
- - - -You can use React state to show a loading state when a form is submitting on the server: - -```tsx filename="pages/index.tsx" switcher -import React, { useState, FormEvent } from 'react' - -export default function Page() { - const [isLoading, setIsLoading] = useState(false) - - async function onSubmit(event: FormEvent) { - event.preventDefault() - setIsLoading(true) // Set loading to true when the request starts - - try { - const formData = new FormData(event.currentTarget) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - // Handle response if necessary - const data = await response.json() - // ... - } catch (error) { - // Handle error if necessary - console.error(error) - } finally { - setIsLoading(false) // Set loading to false when the request completes - } - } - - return ( -
- - -
- ) -} -``` - -```jsx filename="pages/index.jsx" switcher -import React, { useState } from 'react' - -export default function Page() { - const [isLoading, setIsLoading] = useState(false) - - async function onSubmit(event) { - event.preventDefault() - setIsLoading(true) // Set loading to true when the request starts - - try { - const formData = new FormData(event.currentTarget) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - // Handle response if necessary - const data = await response.json() - // ... - } catch (error) { - // Handle error if necessary - console.error(error) - } finally { - setIsLoading(false) // Set loading to false when the request completes - } - } - - return ( -
- - -
- ) -} -``` - -
- -#### Error Handling - - - -Server Actions can also return [serializable objects](https://developer.mozilla.org/docs/Glossary/Serialization). For example, your Server Action might handle errors from creating a new item: +Server Actions can also return [serializable objects](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). For example, your Server Action might handle errors from creating a new item: ```ts filename="app/actions.ts" switcher 'use server' @@ -838,11 +684,165 @@ export default function Page() {
+### Displaying Loading State + + + +Use the React [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a loading state when a form is submitting on the server. The `useFormStatus` hook can only be used as a child of a `form` element using a Server Action. + +For example, the following submit button: + +```tsx filename="app/submit-button.tsx" switcher +'use client' + +import { useFormStatus } from 'react-dom' + +export function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) +} +``` + +```jsx filename="app/submit-button.jsx" switcher +'use client' + +import { useFormStatus } from 'react-dom' + +export function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) +} +``` + +`` can then be used in a form with a Server Action: + +```tsx filename="app/page.tsx" switcher +import { SubmitButton } from '@/app/submit-button' + +export default async function Home() { + return ( +
+ + + + ) +} +``` + +```jsx filename="app/page.jsx" switcher +import { SubmitButton } from '@/app/submit-button' + +export default async function Home() { + return ( +
+ + + + ) +} +``` + +
+ + + +You can use React state to show a loading state when a form is submitting on the server: + +```tsx filename="pages/index.tsx" switcher +import React, { useState, FormEvent } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + + async function onSubmit(event: FormEvent) { + event.preventDefault() + setIsLoading(true) // Set loading to true when the request starts + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // Handle response if necessary + const data = await response.json() + // ... + } catch (error) { + // Handle error if necessary + console.error(error) + } finally { + setIsLoading(false) // Set loading to false when the request completes + } + } + + return ( +
+ + +
+ ) +} +``` + +```jsx filename="pages/index.jsx" switcher +import React, { useState } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + + async function onSubmit(event) { + event.preventDefault() + setIsLoading(true) // Set loading to true when the request starts + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // Handle response if necessary + const data = await response.json() + // ... + } catch (error) { + // Handle error if necessary + console.error(error) + } finally { + setIsLoading(false) // Set loading to false when the request completes + } + } + + return ( +
+ + +
+ ) +} +``` + +
+ -#### Optimistic Updates +### Optimistic Updates -Use [`useOptimistic`](https://react.dev/reference/react/useOptimistic) to optimistically update the UI before the Server Action finishes, rather than waiting for the response: +Use React [`useOptimistic`](https://react.dev/reference/react/useOptimistic) to optimistically update the UI before the Server Action finishes, rather than waiting for the response: ```tsx filename="app/page.tsx" switcher 'use client' @@ -917,32 +917,7 @@ export function Thread({ messages }) { -#### Setting Cookies - - - -You can set cookies inside an API Route using the `setHeader` method on the response: - -```ts filename="pages/api/cookie.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') - res.status(200).send('Cookie has been set.') -} -``` - -```js filename="pages/api/cookie.js" switcher -export default async function handler(req, res) { - res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') - res.status(200).send('Cookie has been set.') -} -``` - - +### Setting Cookies @@ -972,11 +947,9 @@ export async function create() { -#### Reading Cookies - -You can read cookies inside an API Route using the [`cookies`](/docs/pages/building-your-application/routing/api-routes#request-helpers) request helper: +You can set cookies inside an API Route using the `setHeader` method on the response: ```ts filename="pages/api/cookie.ts" switcher import type { NextApiRequest, NextApiResponse } from 'next' @@ -985,20 +958,22 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const auth = req.cookies.authorization - // ... + res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') + res.status(200).send('Cookie has been set.') } ``` ```js filename="pages/api/cookie.js" switcher export default async function handler(req, res) { - const auth = req.cookies.authorization - // ... + res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') + res.status(200).send('Cookie has been set.') } ``` +### Reading Cookies + You can read cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: @@ -1027,11 +1002,9 @@ export async function read() { -#### Deleting Cookies - -You can delete cookies inside an API Route using the `setHeader` method on the response: +You can read cookies inside an API Route using the [`cookies`](/docs/pages/building-your-application/routing/api-routes#request-helpers) request helper: ```ts filename="pages/api/cookie.ts" switcher import type { NextApiRequest, NextApiResponse } from 'next' @@ -1040,20 +1013,22 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') - res.status(200).send('Cookie has been deleted.') + const auth = req.cookies.authorization + // ... } ``` ```js filename="pages/api/cookie.js" switcher export default async function handler(req, res) { - res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') - res.status(200).send('Cookie has been deleted.') + const auth = req.cookies.authorization + // ... } ``` +### Deleting Cookies + You can delete cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: @@ -1084,6 +1059,31 @@ See [additional examples](/docs/app/api-reference/functions/cookies#deleting-coo + + +You can delete cookies inside an API Route using the `setHeader` method on the response: + +```ts filename="pages/api/cookie.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') + res.status(200).send('Cookie has been deleted.') +} +``` + +```js filename="pages/api/cookie.js" switcher +export default async function handler(req, res) { + res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') + res.status(200).send('Cookie has been deleted.') +} +``` + + + ## Calling a Server Action from outside a form ### Examples From a849c3df93c033a828c9a889cb370670f9164273 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 18:04:52 +0000 Subject: [PATCH 11/39] Update links --- .../01-routing/10-route-handlers.mdx | 2 +- .../01-fetching-caching-and-revalidating.mdx | 4 ++-- .../02-data-fetching/02-server-actions-and-mutations.mdx | 4 ++-- .../02-data-fetching/03-patterns.mdx | 2 +- .../03-rendering/02-client-components.mdx | 2 +- .../01-building-your-application/04-caching/index.mdx | 6 +++--- docs/02-app/02-api-reference/04-functions/cookies.mdx | 8 ++++---- .../02-api-reference/04-functions/permanentRedirect.mdx | 4 ++-- docs/02-app/02-api-reference/04-functions/redirect.mdx | 4 ++-- .../02-api-reference/04-functions/server-actions.mdx | 2 +- docs/02-app/index.mdx | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/02-app/01-building-your-application/01-routing/10-route-handlers.mdx b/docs/02-app/01-building-your-application/01-routing/10-route-handlers.mdx index 3da9d0cb5a2e0..419acd83fa398 100644 --- a/docs/02-app/01-building-your-application/01-routing/10-route-handlers.mdx +++ b/docs/02-app/01-building-your-application/01-routing/10-route-handlers.mdx @@ -160,7 +160,7 @@ export async function POST() { } ``` -> **Good to know**: Like API Routes, Route Handlers can be used for cases like handling form submissions. A new abstraction for [handling forms and mutations](/docs/app/building-your-application/data-fetching/forms-and-mutations) that integrates deeply with React is being worked on. +> **Good to know**: Like API Routes, Route Handlers can be used for cases like handling form submissions. A new abstraction for [handling forms and mutations](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) that integrates deeply with React is being worked on. ### Route Resolution diff --git a/docs/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx b/docs/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx index a275d8cf600cc..fea83f08956eb 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx @@ -17,7 +17,7 @@ There are four ways you can fetch data: Next.js extends the native [`fetch` Web API](https://developer.mozilla.org/docs/Web/API/Fetch_API) to allow you to configure the [caching](#caching-data) and [revalidating](#revalidating-data) behavior for each fetch request on the server. React extends `fetch` to automatically [memoize](/docs/app/building-your-application/data-fetching/patterns#fetching-data-where-its-needed) fetch requests while rendering a React component tree. -You can use `fetch` with `async`/`await` in Server Components, in [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and in [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations). +You can use `fetch` with `async`/`await` in Server Components, in [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and in [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). For example: @@ -117,7 +117,7 @@ Learn more about [time-based revalidation](/docs/app/building-your-application/c #### On-demand Revalidation -Data can be revalidated on-demand by path ([`revalidatePath`](/docs/app/api-reference/functions/revalidatePath)) or by cache tag ([`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)) inside a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). +Data can be revalidated on-demand by path ([`revalidatePath`](/docs/app/api-reference/functions/revalidatePath)) or by cache tag ([`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)) inside a [Server Action](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). Next.js has a cache tagging system for invalidating `fetch` requests across routes. diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index f9a6b218d8d8d..31aa58625a417 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -10,7 +10,7 @@ description: Learn how to handle form submissions and data mutations with Next.j ## What are Server Actions? -Server Actions are **asynchronous server functions** that can be called directly from your React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. +Server Actions are **asynchronous server functions** that can be called directly from React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. > **🎥 Watch:** Learn more about forms and mutations with the App Router → [YouTube (10 minutes)](https://youtu.be/dDpZfOQBMaU?si=cJZHlUu_jFhCzHUg). @@ -33,7 +33,7 @@ Forms enable you to create and update data in web applications. Next.js provides > **Good to know:** > -> - We will soon recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router and using [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations#how-server-actions-work) for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route. +> - We will soon recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router and using [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#how-server-actions-work) for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route. > - API Routes [do not specify CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS), meaning they are same-origin only by default. > - Since API Routes run on the server, we're able to use sensitive values (like API keys) through [Environment Variables](/docs/pages/building-your-application/configuring/environment-variables) without exposing them to the client. This is critical for the security of your application. diff --git a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx index 60b97d826cb17..62c42029adf98 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx @@ -17,7 +17,7 @@ Whenever possible, we recommend fetching data on the server. This allows you to: - Reduce client-server [waterfalls](#parallel-and-sequential-data-fetching). - Depending on your region, data fetching can also happen closer to your data source, reducing latency and improving performance. -You can fetch data on the server using Server Components, [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations). +You can fetch data on the server using Server Components, [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). ### Fetching Data Where It's Needed diff --git a/docs/02-app/01-building-your-application/03-rendering/02-client-components.mdx b/docs/02-app/01-building-your-application/03-rendering/02-client-components.mdx index 08e550a52275d..3f6bd3e5f7114 100644 --- a/docs/02-app/01-building-your-application/03-rendering/02-client-components.mdx +++ b/docs/02-app/01-building-your-application/03-rendering/02-client-components.mdx @@ -103,4 +103,4 @@ This means the Client Component JavaScript bundle is downloaded and parsed. Once Sometimes, after you've declared the `"use client"` boundary, you may want to go back to the server environment. For example, you may want to reduce the client bundle size, fetch data on the server, or use an API that is only available on the server. -You can keep code on the server even though it's theoretically nested inside Client Components by interleaving Client and Server Components and [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations). See the [Composition Patterns](/docs/app/building-your-application/rendering/composition-patterns) page for more information. +You can keep code on the server even though it's theoretically nested inside Client Components by interleaving Client and Server Components and [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). See the [Composition Patterns](/docs/app/building-your-application/rendering/composition-patterns) page for more information. diff --git a/docs/02-app/01-building-your-application/04-caching/index.mdx b/docs/02-app/01-building-your-application/04-caching/index.mdx index 2d68f9ab9274c..091960c611956 100644 --- a/docs/02-app/01-building-your-application/04-caching/index.mdx +++ b/docs/02-app/01-building-your-application/04-caching/index.mdx @@ -394,7 +394,7 @@ When configuring the different caching mechanisms, it's important to understand ### Data Cache and Client-side Router cache - Revalidating the Data Cache in a [Route Handler](/docs/app/building-your-application/routing/route-handlers) **will not** immediately invalidate the Router Cache as the Route Handler isn't tied to a specific route. This means Router Cache will continue to serve the previous payload until a hard refresh, or the automatic invalidation period has elapsed. -- To immediately invalidate the Data Cache and Router cache, you can use [`revalidatePath`](#revalidatepath) or [`revalidateTag`](#fetch-optionsnexttags-and-revalidatetag) in a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations). +- To immediately invalidate the Data Cache and Router cache, you can use [`revalidatePath`](#revalidatepath) or [`revalidateTag`](#fetch-optionsnexttags-and-revalidatetag) in a [Server Action](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). ## APIs @@ -500,7 +500,7 @@ revalidateTag('a') There are two places you can use `revalidateTag`, depending on what you're trying to achieve: 1. [Route Handlers](/docs/app/building-your-application/routing/route-handlers) - to revalidate data in response of a third party event (e.g. webhook). This will not invalidate the Router Cache immediately as the Router Handler isn't tied to a specific route. -2. [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations) - to revalidate data after a user action (e.g. form submission). This will invalidate the Router Cache for the associated route. +2. [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) - to revalidate data after a user action (e.g. form submission). This will invalidate the Router Cache for the associated route. ### `revalidatePath` @@ -513,7 +513,7 @@ revalidatePath('/') There are two places you can use `revalidatePath`, depending on what you're trying to achieve: 1. [Route Handlers](/docs/app/building-your-application/routing/route-handlers) - to revalidate data in response to a third party event (e.g. webhook). -2. [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations) - to revalidate data after a user interaction (e.g. form submission, clicking a button). +2. [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) - to revalidate data after a user interaction (e.g. form submission, clicking a button). See the [`revalidatePath` API reference](/docs/app/api-reference/functions/revalidatePath) for more information. diff --git a/docs/02-app/02-api-reference/04-functions/cookies.mdx b/docs/02-app/02-api-reference/04-functions/cookies.mdx index 0ad04f465482b..7e74bf52921a2 100644 --- a/docs/02-app/02-api-reference/04-functions/cookies.mdx +++ b/docs/02-app/02-api-reference/04-functions/cookies.mdx @@ -8,7 +8,7 @@ related: - app/building-your-application/data-fetching/forms-and-mutations --- -The `cookies` function allows you to read the HTTP incoming request cookies from a [Server Component](/docs/app/building-your-application/rendering/server-components) or write outgoing request cookies in a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). +The `cookies` function allows you to read the HTTP incoming request cookies from a [Server Component](/docs/app/building-your-application/rendering/server-components) or write outgoing request cookies in a [Server Action](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). > **Good to know**: `cookies()` is a **[Dynamic Function](/docs/app/building-your-application/rendering/server-components#server-rendering-strategies#dynamic-functions)** whose returned values cannot be known ahead of time. Using it in a layout or page will opt a route into **[dynamic rendering](/docs/app/building-your-application/rendering/server-components#dynamic-rendering)** at request time. @@ -62,7 +62,7 @@ export default function Page() { A method that takes a cookie name, value, and options and sets the outgoing request cookie. -> **Good to know**: HTTP does not allow setting cookies after streaming starts, so you must use `.set()` in a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). +> **Good to know**: HTTP does not allow setting cookies after streaming starts, so you must use `.set()` in a [Server Action](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). ```js filename="app/actions.js" 'use server' @@ -85,7 +85,7 @@ async function create(data) { ## Deleting cookies -> **Good to know**: You can only delete cookies in a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). +> **Good to know**: You can only delete cookies in a [Server Action](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). There are several options for deleting a cookie: @@ -117,7 +117,7 @@ async function create(data) { } ``` -> **Good to know**: `.set()` is only available in a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). +> **Good to know**: `.set()` is only available in a [Server Action](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). ### `cookies().set(name, value, { maxAge: 0 })` diff --git a/docs/02-app/02-api-reference/04-functions/permanentRedirect.mdx b/docs/02-app/02-api-reference/04-functions/permanentRedirect.mdx index cd2279636fdb8..753e380eab674 100644 --- a/docs/02-app/02-api-reference/04-functions/permanentRedirect.mdx +++ b/docs/02-app/02-api-reference/04-functions/permanentRedirect.mdx @@ -3,7 +3,7 @@ title: permanentRedirect description: API Reference for the permanentRedirect function. --- -The `permanentRedirect` function allows you to redirect the user to another URL. `permanentRedirect` can be used in Server Components, Client Components, [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations). +The `permanentRedirect` function allows you to redirect the user to another URL. `permanentRedirect` can be used in Server Components, Client Components, [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). When used in a streaming context, this will insert a meta tag to emit the redirect on the client side. When used in a server action, it will serve a 303 HTTP redirect response to the caller. Otherwise, it will serve a 308 (Permanent) HTTP redirect response to the caller. @@ -24,7 +24,7 @@ permanentRedirect(path, type) | `path` | `string` | The URL to redirect to. Can be a relative or absolute path. | | `type` | `'replace'` (default) or `'push'` (default in Server Actions) | The type of redirect to perform. | -By default, `permanentRedirect` will use `push` (adding a new entry to the browser history stack) in [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations) and `replace` (replacing the current URL in the browser history stack) everywhere else. You can override this behavior by specifying the `type` parameter. +By default, `permanentRedirect` will use `push` (adding a new entry to the browser history stack) in [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) and `replace` (replacing the current URL in the browser history stack) everywhere else. You can override this behavior by specifying the `type` parameter. The `type` parameter has no effect when used in Server Components. diff --git a/docs/02-app/02-api-reference/04-functions/redirect.mdx b/docs/02-app/02-api-reference/04-functions/redirect.mdx index 5ef879da65aac..78979def8cf14 100644 --- a/docs/02-app/02-api-reference/04-functions/redirect.mdx +++ b/docs/02-app/02-api-reference/04-functions/redirect.mdx @@ -3,7 +3,7 @@ title: redirect description: API Reference for the redirect function. --- -The `redirect` function allows you to redirect the user to another URL. `redirect` can be used in Server Components, Client Components, [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations). +The `redirect` function allows you to redirect the user to another URL. `redirect` can be used in Server Components, Client Components, [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). When used in a [streaming context](/docs/app/building-your-application/routing/loading-ui-and-streaming#what-is-streaming), this will insert a meta tag to emit the redirect on the client side. When used in a server action, it will serve a 303 HTTP redirect response to the caller. Otherwise, it will serve a 307 HTTP redirect response to the caller. @@ -24,7 +24,7 @@ redirect(path, type) | `path` | `string` | The URL to redirect to. Can be a relative or absolute path. | | `type` | `'replace'` (default) or `'push'` (default in Server Actions) | The type of redirect to perform. | -By default, `redirect` will use `push` (adding a new entry to the browser history stack) in [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations) and `replace` (replacing the current URL in the browser history stack) everywhere else. You can override this behavior by specifying the `type` parameter. +By default, `redirect` will use `push` (adding a new entry to the browser history stack) in [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) and `replace` (replacing the current URL in the browser history stack) everywhere else. You can override this behavior by specifying the `type` parameter. The `type` parameter has no effect when used in Server Components. diff --git a/docs/02-app/02-api-reference/04-functions/server-actions.mdx b/docs/02-app/02-api-reference/04-functions/server-actions.mdx index 01c275d6c3ead..13fde9b8fe580 100644 --- a/docs/02-app/02-api-reference/04-functions/server-actions.mdx +++ b/docs/02-app/02-api-reference/04-functions/server-actions.mdx @@ -11,7 +11,7 @@ related: {/* TODO: This page will need to link back to React docs mentioned at the bottom */} -Next.js integrates with React Actions to provide a built-in solution for [server mutations](/docs/app/building-your-application/data-fetching/forms-and-mutations). +Next.js integrates with React Actions to provide a built-in solution for [server mutations](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). ## Convention diff --git a/docs/02-app/index.mdx b/docs/02-app/index.mdx index 1724fdea5eaa2..4b203ec40cf03 100644 --- a/docs/02-app/index.mdx +++ b/docs/02-app/index.mdx @@ -44,7 +44,7 @@ Here are some common authentication solutions that support the App Router: ### How can I set cookies? -You can set cookies in [Server Actions](/docs/app/building-your-application/data-fetching/forms-and-mutations#setting-cookies) or [Route Handlers](/docs/app/building-your-application/routing/route-handlers) using the [`cookies`](/docs/app/api-reference/functions/cookies) function. +You can set cookies in [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#setting-cookies) or [Route Handlers](/docs/app/building-your-application/routing/route-handlers) using the [`cookies`](/docs/app/api-reference/functions/cookies) function. Since HTTP does not allow setting cookies after streaming starts, you cannot set cookies from a page or layout directly. You can also set cookies from [Middleware](/docs/app/building-your-application/routing/middleware#using-cookies). @@ -64,7 +64,7 @@ Yes. You can view [Next.js Commerce](https://vercel.com/templates/next.js/nextjs - [Routing Fundamentals](/docs/app/building-your-application/routing) - [Data Fetching, Caching, and Revalidating](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating) -- [Forms and Mutations](/docs/app/building-your-application/data-fetching/forms-and-mutations) +- [Forms and Mutations](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) - [Caching](/docs/app/building-your-application/caching) - [Rendering Fundamentals](/docs/app/building-your-application/rendering) - [Server Components](/docs/app/building-your-application/rendering/server-components) From 09dd302eb9f6948122a80651961bf4e5b3e67588 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 19:07:53 +0000 Subject: [PATCH 12/39] Fix broken links --- docs/02-app/02-api-reference/04-functions/cookies.mdx | 2 +- docs/02-app/02-api-reference/04-functions/server-actions.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/02-app/02-api-reference/04-functions/cookies.mdx b/docs/02-app/02-api-reference/04-functions/cookies.mdx index 7e74bf52921a2..0422205fffbbd 100644 --- a/docs/02-app/02-api-reference/04-functions/cookies.mdx +++ b/docs/02-app/02-api-reference/04-functions/cookies.mdx @@ -5,7 +5,7 @@ related: title: Next Steps description: For more information on what to do next, we recommend the following sections links: - - app/building-your-application/data-fetching/forms-and-mutations + - app/building-your-application/data-fetching/server-actions-and-mutations --- The `cookies` function allows you to read the HTTP incoming request cookies from a [Server Component](/docs/app/building-your-application/rendering/server-components) or write outgoing request cookies in a [Server Action](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). diff --git a/docs/02-app/02-api-reference/04-functions/server-actions.mdx b/docs/02-app/02-api-reference/04-functions/server-actions.mdx index 13fde9b8fe580..44c23620a49d2 100644 --- a/docs/02-app/02-api-reference/04-functions/server-actions.mdx +++ b/docs/02-app/02-api-reference/04-functions/server-actions.mdx @@ -6,7 +6,7 @@ related: title: Next Steps description: For more information on what to do next, we recommend the following sections links: - - app/building-your-application/data-fetching/forms-and-mutations + - app/building-your-application/data-fetching/server-actions-and-mutations --- {/* TODO: This page will need to link back to React docs mentioned at the bottom */} From 2eee2b4cdce631353d0cdf744117c9d2f88829f0 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 19:12:13 +0000 Subject: [PATCH 13/39] Add placeholder for allowedOrigins --- .../05-next-config-js/serverActions.mdx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx diff --git a/docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx b/docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx new file mode 100644 index 0000000000000..4d6c565013419 --- /dev/null +++ b/docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx @@ -0,0 +1,21 @@ +--- +title: serverActions +description: x +--- + +# Options + +## `allowedOrigins` + +List of safe origin domains from which Server Actions can be invoked. If not provided, only the same origin is allowed. + +```js filename="next.config.js" +/** @type {import('next').NextConfig} */ +module.exports = { + experimental: { + serverActions: { + allowedOrigins: ['my-proxy.com'], + }, + }, +} +``` From 23e08cdcd1f3ca9d959c456c0c789a17069d9937 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 19:12:31 +0000 Subject: [PATCH 14/39] Tweaks --- .../02-server-actions-and-mutations.mdx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 31aa58625a417..1b23989e05885 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -8,18 +8,16 @@ description: Learn how to handle form submissions and data mutations with Next.j -## What are Server Actions? - -Server Actions are **asynchronous server functions** that can be called directly from React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. +Server Actions are **asynchronous server functions** that can be invoked from React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. > **🎥 Watch:** Learn more about forms and mutations with the App Router → [YouTube (10 minutes)](https://youtu.be/dDpZfOQBMaU?si=cJZHlUu_jFhCzHUg). ## How Server Actions work -- Server Actions are a React feature that integrates deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When an action is called, Next.js can return both the updated UI and new data in a single server round trip. -- Server Actions can be defined inline in Server Components or called from Client Components. -- In Server Components, Server Actions can be used to handle forms without JavaScript, providing progressive enhancement. -- In Client Components, forms calling Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. +- Server Actions are a React feature that integrates deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server round trip. +- Server Actions can be defined inline in Server Components or invoked from Client Components. +- In Server Components, forms invoking Server Actions support progressive enhancement by default. +- In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. - Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple actions per route. The browser also does not refresh on form submission. - Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. - Server Actions inherit the [runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes) from the page or layout they are used on. @@ -43,7 +41,7 @@ Forms enable you to create and update data in web applications. Next.js provides -A Server Action can be defined with the React [´"use server"`](https://react.dev/reference/react/use-server) directive. By adding `"use server"` directive, the function will execute on the server regardless of whether it's called from a Server or Client Component. +A Server Action can be defined with the React [´"use server"`](https://react.dev/reference/react/use-server) directive. By adding `"use server"` directive, the function will execute on the server regardless of whether it's invoked from a Server or Client Component. In Server Components, the action can be inlined and the `"use server"` added at the top of the function body: @@ -101,9 +99,9 @@ export async function create(formData) { -React extends the HTML [`
`](https://developer.mozilla.org/docs/Web/HTML/Element/form) element to allow Server Actions to be called with the `action` prop. +React extends the HTML [``](https://developer.mozilla.org/docs/Web/HTML/Element/form) element to allow Server Actions to be invoked with the `action` prop. -When called in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object containing the captured form data. You can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): +When invoked in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object containing the captured form data. You can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): ```tsx filename="app/invoices/page.tsx" switcher export default function Page() { From 5bedb176b86b0d19645976583c067718c28ca109 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Wed, 29 Nov 2023 19:14:03 +0000 Subject: [PATCH 15/39] Add "Built-in CSRF Protection" section --- .../02-server-actions-and-mutations.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 1b23989e05885..ca57b037ecf59 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -1095,3 +1095,22 @@ export default async function handler(req, res) { ### Built-in CSRF Protection ### Setting Encryption Keys + +All Server Actions can be invoked by plain ``, which could open them up to [CSRF attacks](https://developer.mozilla.org/en-US/docs/Glossary/CSRF). + +Behind the scenes, Server Actions use the `POST` method, and only this HTTP method is allowed to invoke them. This prevents most CSRF vulnerabilities in modern browsers, particularly with [SameSite cookies](https://web.dev/articles/samesite-cookies-explained) being the default. + +As an additional protection, Server Actions in Next.js also compares the [Origin header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin) to the [Host header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) (or `X-Forwarded-Host`). If these don't match, the request will be aborted. In other words, Server Actions can only be invoked on the same host as the page that hosts it. + +For large applications and organizations that use reverse proxies or multi-layered backend architectures (where the server API differs from the production domain), it's recommended to use the Next Config [`serverActions.allowedOrigins`](/docs/app/api-reference/next-config-js/serverActions) option to specify a list of safe origins. The option accepts an array of strings. + +```js filename="next.config.js" +/** @type {import('next').NextConfig} */ +module.exports = { + experimental: { + serverActions: { + allowedOrigins: ['my-proxy.com'], + }, + }, +} +``` From bf3f619ed789797e6f98ea3f99f87e9eb486ca23 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Thu, 30 Nov 2023 11:34:32 +0000 Subject: [PATCH 16/39] Split /app and /pages content to make it easier to edit --- .../02-server-actions-and-mutations.mdx | 1060 +++++++---------- .../03-forms-and-mutations.mdx | 402 ++++++- 2 files changed, 817 insertions(+), 645 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index ca57b037ecf59..9acc692fa2f15 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -4,44 +4,26 @@ nav_title: Server Actions and Mutations description: Learn how to handle form submissions and data mutations with Next.js. --- -{/* The content of this doc is shared between the app and pages router. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */} - - - Server Actions are **asynchronous server functions** that can be invoked from React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. > **🎥 Watch:** Learn more about forms and mutations with the App Router → [YouTube (10 minutes)](https://youtu.be/dDpZfOQBMaU?si=cJZHlUu_jFhCzHUg). ## How Server Actions work -- Server Actions are a React feature that integrates deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server round trip. +- Server Actions integrate deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server round trip. - Server Actions can be defined inline in Server Components or invoked from Client Components. -- In Server Components, forms invoking Server Actions support progressive enhancement by default. -- In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. -- Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple actions per route. The browser also does not refresh on form submission. + - In Server Components, forms invoking Server Actions support progressive enhancement by default. + - In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. +- Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple reusable actions per route. The browser also does not refresh on form submission. +- The return value of Server Actions must be serializable by React. See the React docs for a list of [serializable arguments and values](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). - Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. - Server Actions inherit the [runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes) from the page or layout they are used on. -- The return value of Server Actions must be serializable by React. See the React docs for a list of [serializable arguments and values](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). - - - - - -Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using **API Routes**. - -> **Good to know:** -> -> - We will soon recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router and using [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#how-server-actions-work) for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route. -> - API Routes [do not specify CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS), meaning they are same-origin only by default. -> - Since API Routes run on the server, we're able to use sensitive values (like API keys) through [Environment Variables](/docs/pages/building-your-application/configuring/environment-variables) without exposing them to the client. This is critical for the security of your application. - - ## Examples - +A Server Action can be defined with the React [´"use server"`](https://react.dev/reference/react/use-server) directive. This denotes the function will execute on the server regardless of whether it's invoked from a Server or Client Component. -A Server Action can be defined with the React [´"use server"`](https://react.dev/reference/react/use-server) directive. By adding `"use server"` directive, the function will execute on the server regardless of whether it's invoked from a Server or Client Component. +### Server Components In Server Components, the action can be inlined and the `"use server"` added at the top of the function body: @@ -75,6 +57,8 @@ export default function Page() { } ``` +### Client Components + To call a Server Action in a Client Component, create a new file and add the `"use server"` directive at the top of it. All functions within the file will be marked as Server Actions that can be reused in both Client and Server Components: ```tsx filename="app/actions.ts" switcher @@ -93,15 +77,11 @@ export async function create(formData) { } ``` - - -### Server-only Forms - - +### Forms React extends the HTML [``](https://developer.mozilla.org/docs/Web/HTML/Element/form) element to allow Server Actions to be invoked with the `action` prop. -When invoked in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object containing the captured form data. You can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): +When invoked in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object containing the form data. This means you don't have to use React `useState`, instead you can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): ```tsx filename="app/invoices/page.tsx" switcher export default function Page() { @@ -143,245 +123,18 @@ export default function Page() { > **Good to know:** When working with forms that have many fields, you may want to consider using the [`entries()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries) method with JavaScript's [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries). For example: `const rawFormData = Object.fromEntries(formData.entries())` -To learn more about the `"use server"` directive and forms in React, see the following resources: +To learn more about the `"use server"` directive and the `` element in React, see the following resources: - [React `` documentation](https://react.dev/reference/react-dom/components/form#handle-form-submission-with-a-server-action) - [React `use server` documentation](https://react.dev/reference/react/use-server) - Next.js Example: [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms) - - - - -With the Pages Router, you need to manually create API endpoints to handle securely mutating data on the server. - -```ts filename="pages/api/submit.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const data = req.body - const id = await createItem(data) - res.status(200).json({ id }) -} -``` - -```js filename="pages/api/submit.js" switcher -export default function handler(req, res) { - const data = req.body - const id = await createItem(data) - res.status(200).json({ id }) -} -``` - -Then, call the API Route from the client with an event handler: - -```tsx filename="pages/index.tsx" switcher -import { FormEvent } from 'react' - -export default function Page() { - async function onSubmit(event: FormEvent) { - event.preventDefault() - - const formData = new FormData(event.currentTarget) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - // Handle response if necessary - const data = await response.json() - // ... - } - - return ( - - - - - ) -} -``` - -```jsx filename="pages/index.jsx" switcher -export default function Page() { - async function onSubmit(event) { - event.preventDefault() - - const formData = new FormData(event.target) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - // Handle response if necessary - const data = await response.json() - // ... - } - - return ( -
- - -
- ) -} -``` - -
- - - -### Revalidating Data - -Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-your-application/caching) on demand. You can invalidate an entire route segment with [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath): - -```ts filename="app/actions.ts" switcher -'use server' - -import { revalidatePath } from 'next/cache' - -export default async function submit() { - try { - // ... - } catch (error) { - // ... - } - - revalidatePath('/posts') -} -``` - -```js filename="app/actions.js" switcher -'use server' - -import { revalidatePath } from 'next/cache' - -export default async function submit() { - try { - // ... - } catch (error) { - // ... - } - - revalidatePath('/posts') -} -``` - -Or invalidate a specific data fetch with a cache tag using [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag): - -```ts filename="app/actions.ts" switcher -'use server' - -import { revalidateTag } from 'next/cache' - -export default async function submit() { - try { - // ... - } catch (error) { - // ... - } - - revalidateTag('posts') -} -``` - -```js filename="app/actions.js" switcher -'use server' - -import { revalidateTag } from 'next/cache' - -export default async function submit() { - try { - // ... - } catch (error) { - // ... - } - revalidateTag('posts') -} -``` - - - -### Redirecting - - - -If you would like to redirect the user to a different route after the completion of a Server Action, you can use [`redirect`](/docs/app/api-reference/functions/redirect) and any absolute or relative URL: - -```ts filename="app/actions.ts" switcher -'use server' - -import { redirect } from 'next/navigation' -import { revalidateTag } from 'next/cache' - -export default async function submit(id: string) { - try { - // ... - } catch (error) { - // ... - } - - revalidateTag('posts') // Update cached posts - redirect(`/post/${id}`) // Navigate to the new post page -} -``` - -```js filename="app/actions.js" switcher -'use server' - -import { redirect } from 'next/navigation' -import { revalidateTag } from 'next/cache' - -export default async function submit(id) { - try { - // ... - } catch (error) { - // ... - } - - revalidateTag('posts') // Update cached posts - redirect(`/post/${id}`) // Navigate to the new post page -} -``` - - - - - -If you would like to redirect the user to a different route after a mutation, you can [`redirect`](/docs/pages/building-your-application/routing/api-routes#response-helpers) to any absolute or relative URL: - -```ts filename="pages/api/submit.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const id = await addPost() - res.redirect(307, `/post/${id}`) -} -``` - -```js filename="pages/api/submit.js" switcher -export default async function handler(req, res) { - const id = await addPost() - res.redirect(307, `/post/${id}`) -} -``` - - - -### Server-side Form Validation +#### Form Validation We recommend using HTML validation like `required` and `type="email"` for basic client-side form validation. For more advanced server-side validation, you can use a schema validation library like [zod](https://zod.dev/) to validate the form fields before mutating the data: - - ```tsx filename="app/actions.ts" switcher import { z } from 'zod' @@ -434,45 +187,73 @@ export default async function submit(formData) { Please refer to the [Zod documentation](https://zod.dev/) for more information. - +#### Displaying a Loading State with `useFormStatus` - +Use the React [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a loading state when a form is submitting on the server. The `useFormStatus` hook can only be used as a child of a `form` element using a Server Action. -```ts filename="pages/api/submit.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' -import { z } from 'zod' +For example, the following submit button: -const schema = z.object({ - // ... -}) +```tsx filename="app/submit-button.tsx" switcher +'use client' -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const parsed = schema.parse(req.body) - // ... +import { useFormStatus } from 'react-dom' + +export function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) } ``` -```js filename="pages/api/submit.js" switcher -import { z } from 'zod' +```jsx filename="app/submit-button.jsx" switcher +'use client' -const schema = z.object({ - // ... -}) +import { useFormStatus } from 'react-dom' -export default async function handler(req, res) { - const parsed = schema.parse(req.body) - // ... +export function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) } ``` - +`` can then be used in a form with a Server Action: + +```tsx filename="app/page.tsx" switcher +import { SubmitButton } from '@/app/submit-button' + +export default async function Home() { + return ( +
+ + + + ) +} +``` + +```jsx filename="app/page.jsx" switcher +import { SubmitButton } from '@/app/submit-button' -### Error Handling +export default async function Home() { + return ( +
+ + + + ) +} +``` - +#### Error Handling Server Actions can also return [serializable objects](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). For example, your Server Action might handle errors from creating a new item: @@ -576,290 +357,28 @@ export function AddForm() { } ``` - +#### Optimistic Updates - +Use React [`useOptimistic`](https://react.dev/reference/react/useOptimistic) to optimistically update the UI before the Server Action finishes, rather than waiting for the response: -You can use React state to show an error message when a form submission fails: +```tsx filename="app/page.tsx" switcher +'use client' -```tsx filename="pages/index.tsx" switcher -import React, { useState, FormEvent } from 'react' +import { useOptimistic } from 'react' +import { send } from './actions' -export default function Page() { - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(null) - - async function onSubmit(event: FormEvent) { - event.preventDefault() - setIsLoading(true) - setError(null) // Clear previous errors when a new request starts - - try { - const formData = new FormData(event.currentTarget) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - if (!response.ok) { - throw new Error('Failed to submit the data. Please try again.') - } - - // Handle response if necessary - const data = await response.json() - // ... - } catch (error) { - // Capture the error message to display to the user - setError(error.message) - console.error(error) - } finally { - setIsLoading(false) - } - } - - return ( -
- {error &&
{error}
} -
- - -
-
- ) -} -``` - -```jsx filename="pages/index.jsx" switcher -import React, { useState } from 'react' - -export default function Page() { - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(null) - - async function onSubmit(event) { - event.preventDefault() - setIsLoading(true) - setError(null) // Clear previous errors when a new request starts - - try { - const formData = new FormData(event.currentTarget) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - if (!response.ok) { - throw new Error('Failed to submit the data. Please try again.') - } - - // Handle response if necessary - const data = await response.json() - // ... - } catch (error) { - // Capture the error message to display to the user - setError(error.message) - console.error(error) - } finally { - setIsLoading(false) - } - } - - return ( -
- {error &&
{error}
} -
- - -
-
- ) -} -``` - -
- -### Displaying Loading State - - - -Use the React [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a loading state when a form is submitting on the server. The `useFormStatus` hook can only be used as a child of a `form` element using a Server Action. - -For example, the following submit button: - -```tsx filename="app/submit-button.tsx" switcher -'use client' - -import { useFormStatus } from 'react-dom' - -export function SubmitButton() { - const { pending } = useFormStatus() - - return ( - - ) -} -``` - -```jsx filename="app/submit-button.jsx" switcher -'use client' - -import { useFormStatus } from 'react-dom' - -export function SubmitButton() { - const { pending } = useFormStatus() - - return ( - - ) -} -``` - -`` can then be used in a form with a Server Action: - -```tsx filename="app/page.tsx" switcher -import { SubmitButton } from '@/app/submit-button' - -export default async function Home() { - return ( -
- - - - ) -} -``` - -```jsx filename="app/page.jsx" switcher -import { SubmitButton } from '@/app/submit-button' - -export default async function Home() { - return ( -
- - - - ) -} -``` - -
- - - -You can use React state to show a loading state when a form is submitting on the server: - -```tsx filename="pages/index.tsx" switcher -import React, { useState, FormEvent } from 'react' - -export default function Page() { - const [isLoading, setIsLoading] = useState(false) - - async function onSubmit(event: FormEvent) { - event.preventDefault() - setIsLoading(true) // Set loading to true when the request starts - - try { - const formData = new FormData(event.currentTarget) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - // Handle response if necessary - const data = await response.json() - // ... - } catch (error) { - // Handle error if necessary - console.error(error) - } finally { - setIsLoading(false) // Set loading to false when the request completes - } - } - - return ( -
- - -
- ) -} -``` - -```jsx filename="pages/index.jsx" switcher -import React, { useState } from 'react' - -export default function Page() { - const [isLoading, setIsLoading] = useState(false) - - async function onSubmit(event) { - event.preventDefault() - setIsLoading(true) // Set loading to true when the request starts - - try { - const formData = new FormData(event.currentTarget) - const response = await fetch('/api/submit', { - method: 'POST', - body: formData, - }) - - // Handle response if necessary - const data = await response.json() - // ... - } catch (error) { - // Handle error if necessary - console.error(error) - } finally { - setIsLoading(false) // Set loading to false when the request completes - } - } - - return ( -
- - -
- ) -} -``` - -
- - - -### Optimistic Updates - -Use React [`useOptimistic`](https://react.dev/reference/react/useOptimistic) to optimistically update the UI before the Server Action finishes, rather than waiting for the response: - -```tsx filename="app/page.tsx" switcher -'use client' - -import { useOptimistic } from 'react' -import { send } from './actions' - -type Message = { - message: string -} - -export function Thread({ messages }: { messages: Message[] }) { - const [optimisticMessages, addOptimisticMessage] = useOptimistic( - messages, - (state: Message[], newMessage: string) => [ - ...state, - { message: newMessage }, - ] - ) +type Message = { + message: string +} + +export function Thread({ messages }: { messages: Message[] }) { + const [optimisticMessages, addOptimisticMessage] = useOptimistic( + messages, + (state: Message[], newMessage: string) => [ + ...state, + { message: newMessage }, + ] + ) return (
@@ -913,77 +432,129 @@ export function Thread({ messages }) { } ``` - +### Revalidating Data -### Setting Cookies +Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-your-application/caching) on demand. You can invalidate an entire route segment with [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath): - +```ts filename="app/actions.ts" switcher +'use server' -You can set cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: +import { revalidatePath } from 'next/cache' + +export default async function submit() { + try { + // ... + } catch (error) { + // ... + } + + revalidatePath('/posts') +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { revalidatePath } from 'next/cache' + +export default async function submit() { + try { + // ... + } catch (error) { + // ... + } + + revalidatePath('/posts') +} +``` + +Or invalidate a specific data fetch with a cache tag using [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag): ```ts filename="app/actions.ts" switcher 'use server' -import { cookies } from 'next/headers' +import { revalidateTag } from 'next/cache' -export async function create() { - const cart = await createCart() - cookies().set('cartId', cart.id) +export default async function submit() { + try { + // ... + } catch (error) { + // ... + } + + revalidateTag('posts') } ``` ```js filename="app/actions.js" switcher 'use server' -import { cookies } from 'next/headers' +import { revalidateTag } from 'next/cache' -export async function create() { - const cart = await createCart() - cookies().set('cartId', cart.id) +export default async function submit() { + try { + // ... + } catch (error) { + // ... + } + revalidateTag('posts') } ``` - +### Redirecting + +If you would like to redirect the user to a different route after the completion of a Server Action, you can use [`redirect`](/docs/app/api-reference/functions/redirect) and any absolute or relative URL: - +```ts filename="app/actions.ts" switcher +'use server' -You can set cookies inside an API Route using the `setHeader` method on the response: +import { redirect } from 'next/navigation' +import { revalidateTag } from 'next/cache' -```ts filename="pages/api/cookie.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' +export default async function submit(id: string) { + try { + // ... + } catch (error) { + // ... + } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') - res.status(200).send('Cookie has been set.') + revalidateTag('posts') // Update cached posts + redirect(`/post/${id}`) // Navigate to the new post page } ``` -```js filename="pages/api/cookie.js" switcher -export default async function handler(req, res) { - res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') - res.status(200).send('Cookie has been set.') +```js filename="app/actions.js" switcher +'use server' + +import { redirect } from 'next/navigation' +import { revalidateTag } from 'next/cache' + +export default async function submit(id) { + try { + // ... + } catch (error) { + // ... + } + + revalidateTag('posts') // Update cached posts + redirect(`/post/${id}`) // Navigate to the new post page } ``` - - -### Reading Cookies +### Cookies - +#### Setting Cookies -You can read cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: +You can set cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: ```ts filename="app/actions.ts" switcher 'use server' import { cookies } from 'next/headers' -export async function read() { - const auth = cookies().get('authorization')?.value - // ... +export async function create() { + const cart = await createCart() + cookies().set('cartId', cart.id) } ``` @@ -992,43 +563,40 @@ export async function read() { import { cookies } from 'next/headers' -export async function read() { - const auth = cookies().get('authorization')?.value - // ... +export async function create() { + const cart = await createCart() + cookies().set('cartId', cart.id) } ``` - +#### Reading Cookies - +You can read cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: -You can read cookies inside an API Route using the [`cookies`](/docs/pages/building-your-application/routing/api-routes#request-helpers) request helper: +```ts filename="app/actions.ts" switcher +'use server' -```ts filename="pages/api/cookie.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' +import { cookies } from 'next/headers' -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const auth = req.cookies.authorization +export async function read() { + const auth = cookies().get('authorization')?.value // ... } ``` -```js filename="pages/api/cookie.js" switcher -export default async function handler(req, res) { - const auth = req.cookies.authorization +```js filename="app/actions.js" switcher +'use server' + +import { cookies } from 'next/headers' + +export async function read() { + const auth = cookies().get('authorization')?.value // ... } ``` - - ### Deleting Cookies - - You can delete cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: ```ts filename="app/actions.ts" switcher @@ -1055,46 +623,178 @@ export async function delete() { See [additional examples](/docs/app/api-reference/functions/cookies#deleting-cookies) for deleting cookies from Server Actions. - +## Security + +#### Using React's `taintObjectReference` API + +For initial load Next.js will run both the Server Components and the Client Components on the server to produce HTML. + +Server Components (RSC) execute in a separate module system from the Client Components to avoid accidentally exposing information between the two modules. - +Client Components that render through Server-side Rendering (SSR) should be considered as the same security policy as the browser client. It should not gain access to any privileged data or private APIs. It's highly discouraged to use hacks to try to circumvent this protection (such as stashing data on the global object). The principle is that this code should be able to execute the same on the server as the client. In alignment with secure by default practices, Next.js will fail the build if server-only modules are imported from a Client Component. -You can delete cookies inside an API Route using the `setHeader` method on the response: +Recommend React taint API - general, users should not be relying on the encryption alone -```ts filename="pages/api/cookie.ts" switcher -import type { NextApiRequest, NextApiResponse } from 'next' +As we cannot control what data get sent to the client +Encryption is the last resort - for enterprise -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') - res.status(200).send('Cookie has been deleted.') +In this approach you'll want to audit your "use client" files carefully. While auditing and reviewing PRs, look at all the exported functions and if the type signature accepts overly broad objects like User, or contains props like token or creditCard. Even privacy sensitive fields like phoneNumber need extra scrutiny. A Client Component should not accept more data than the minimal data it needs to perform its job. + +A Client Component should not accept more data than the minimal data it needs to perform its job. + +```jsx +import Profile from './components/profile.tsx' + +export async function Page({ params: { slug } }) { + const [rows] = await sql`SELECT * FROM user WHERE slug = ${slug}` + const userData = rows[0] + // EXPOSED: This exposes all the fields in userData to the client because + // we are passing the data from the Server Component to the Client. + // This is similar to returning `userData` in `getServerSideProps` + return } ``` -```js filename="pages/api/cookie.js" switcher -export default async function handler(req, res) { - res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') - res.status(200).send('Cookie has been deleted.') +```jsx +'use client' +// BAD: This is a bad props interface because it accepts way more data than the +// Client Component needs and it encourages server components to pass all that +// data down. A better solution would be to accept a limited object with just +// the fields necessary for rendering the profile. +export default async function Profile({ user }: { user: User }) { + return ( +
+

{user.name}

+ ... +
+ ) } ``` -
+Server Only +Code that should only ever execute on the server can be marked with: -## Calling a Server Action from outside a form +import 'server-only'; +This will cause the build to error if a Client Component tries to import this module. This can be used to ensure that proprietary/sensitive code or internal business logic doesn't accidentally leak to the client. -### Examples +The primary way to transfer data is using the React Server Components protocol which happens automatically when passing props to the Client Components. This serialization supports a superset of JSON. Transferring custom classes is not supported and will result in an error. -#### `useEffect +Therefore, a nice trick to avoid too large objects being accidentally exposed to the client is to use class for your data access records. -## Security +In the upcoming Next.js 14 release, you can also try out the experimental React Taint APIs by enable the taint flag in next.config.js. -### Using React's `taintObjectReference` API +```js +module.exports = { + experimental: { + taint: true, + }, +} +``` -### Built-in CSRF Protection +This lets you mark an object that should not be allowed to be passed to the client as is. + +app/data.ts + +```jsx +import { experimental_taintObjectReference } from 'react'; + +export async function getUserData(id) { + const data = ...; + experimental_taintObjectReference( + 'Do not pass user data to the client', + data + ); + return data; +} +``` + +app/page.tsx + +import { getUserData } from './data'; + +export async function Page({ searchParams }) { + const userData = getUserData(searchParams.id); + return ; // error +} +This does not protect against extracting data fields out of this object and passing them along: + +app/page.tsx + +export async function Page({ searchParams }) { + const { name, phone } = getUserData(searchParams.id); + // Intentionally exposing personal data + return ; +} +For unique strings such as tokens, the raw value can be blocked as well using taintUniqueValue. -### Setting Encryption Keys +app/data.ts + +import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react'; + +export async function getUserData(id) { + const data = ...; + experimental_taintObjectReference( + 'Do not pass user data to the client', + data + ); + experimental_taintUniqueValue( + 'Do not pass tokens to the client', + data, + data.token + ); + return data; +} +However, even this doesn't block derived values. + +It's better to avoid data getting into the Server Components in the first place - using a Data Access Layer. Taint checking provides an additional layer of protection against mistakes by specifying the value, please be mindful that functions and classes are already blocked from being passed to Client Components. More layers the minimize risk of something slipping through. + +By default, environment variables are only available on the Server. By convention, Next.js also exposes any environment variable prefixed with NEXT*PUBLIC* to the client. This lets you expose certain explicit configuration that should be available to the client. + +### Encryption + +Encryption for variables that are sent to the client, user might not be aware of it as it’s automated + +If you define an action inside a server component, + +When you reference variables outside “use server”. + +That works automatically, when deploying to Vercel and other platforms + +All this works outside of Vercel + +Encryption key is changed every time you build your application, meaning redeploying + +Prob: If you self host your application in multiple servers, each will get a different key + +process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY - if you specify this variable, you can make sure your keys are identical on each server. + +More of an advanced use case. + +By default a private key is generated automatically during the build of a Next.js project. Each rebuild generates a new private key which means that each Server Action can only be invoked for a specific build. You might want to use Skew Protection to ensure that you always invoke the correction version during redeploys. + +If you need a key that rotates more frequently or is persistent across multiple builds, you can configure it manually using NEXT_SERVER_ACTIONS_ENCRYPTION_KEY environment variable. + +By encrypting all closed over variables, you don't accidentally expose any secrets in them. By signing it, it makes it harder for an attacker to mess with the input to the action. + +Another alternative to using closures is to use the .bind(...) function in JavaScript. These are NOT encrypted. This provides an opt-out for performance and is also consistent with .bind() on the client. + +app/page.tsx + +async function deletePost(id: number) { +"use server"; +// verify id and that you can still delete it +... +} + +export async function Page({ slug }) { + const post = await getPost(slug); + return ; +} +The principle is that the argument list to Server Actions ("use server") must always be treated as hostile and the input has to be verified. + +### Built-in CSRF Protection All Server Actions can be invoked by plain `
`, which could open them up to [CSRF attacks](https://developer.mozilla.org/en-US/docs/Glossary/CSRF). @@ -1114,3 +814,77 @@ module.exports = { }, } ``` + +serverActions.allowedOrigins and process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY are more advanced cases + +Planning to have a call with @Delba on the details - here’s a quick outline: + +non-form action calls + +use try-catch to handle errors (or return an error code) + +errors are hidden with a digest + +security + +variables from the closure are encrypted + +all automatic, rotates on new builds, use process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY to override the encryption key (when self-hosting on multiple servers) + +always use the React taint API to prevent accidental leaks. encryption is the last resort + +CSRF protection - next.js ensures the action request is coming from the correct domain + +use serverActions.allowedOrigins to specify safe origins + +link to seb’s security blog post + +New APIs + +Edge Cases / Examples: + +We have examples of calling actions in forms + +But it would be good to add examples of calling actions outside forms + +Called as normal async functions - await, try/catch + +Limitations: The argument to the action call + +Other arguments you pass to the action should be serializable + +But you can return React Elements, what would be a use case for this? + +React will throw the error inside the action again + +Actions triggered in useEffect() or 3rd party libraries. + +For non-user interactions, but will trigger a mutation + +No need to add startTransition in the action call + +Example for useOptimistic API, can + +Triggering actions in a declarative way, state + +Server Actions DX + +Right now, server actions are requests in the network tab, there’s not difference + +Maybe with the dev tools, we can make it easier to debug + +Adding API to middleware, so you can have request.action in your middleware. + +Action name, if the function has it + +Server Actions can be anonymous functions + +https://github.com/vercel/next.js/pull/58023 + +https://github.com/vercel/next.js/pull/58023/files#diff-5c3571605c187caaae3f7f40595b8eff5c9b38fb3130e41f50423469dbd0237b + +https://react.dev/reference/react/useOptimistic + +``` + +``` diff --git a/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx b/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx index 6b7fbd99f4ff8..251dc911df736 100644 --- a/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx +++ b/docs/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx @@ -2,7 +2,405 @@ title: Forms and Mutations nav_title: Forms and Mutations description: Learn how to handle form submissions and data mutations with Next.js. -source: app/building-your-application/data-fetching/server-actions-and-mutations --- -{/* DO NOT EDIT. The content of this doc is generated from the source above. To edit the content of this page, navigate to the source page in your editor. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */} +Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using **API Routes**. + +> **Good to know:** +> +> - We will soon recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router and using [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#how-server-actions-work) for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route. +> - API Routes [do not specify CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS), meaning they are same-origin only by default. +> - Since API Routes run on the server, we're able to use sensitive values (like API keys) through [Environment Variables](/docs/pages/building-your-application/configuring/environment-variables) without exposing them to the client. This is critical for the security of your application. + +## Examples + +### Server-only Form + +With the Pages Router, you need to manually create API endpoints to handle securely mutating data on the server. + +```ts filename="pages/api/submit.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const data = req.body + const id = await createItem(data) + res.status(200).json({ id }) +} +``` + +```js filename="pages/api/submit.js" switcher +export default function handler(req, res) { + const data = req.body + const id = await createItem(data) + res.status(200).json({ id }) +} +``` + +Then, call the API Route from the client with an event handler: + +```tsx filename="pages/index.tsx" switcher +import { FormEvent } from 'react' + +export default function Page() { + async function onSubmit(event: FormEvent) { + event.preventDefault() + + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // Handle response if necessary + const data = await response.json() + // ... + } + + return ( + + + + + ) +} +``` + +```jsx filename="pages/index.jsx" switcher +export default function Page() { + async function onSubmit(event) { + event.preventDefault() + + const formData = new FormData(event.target) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // Handle response if necessary + const data = await response.json() + // ... + } + + return ( +
+ + +
+ ) +} +``` + +## Form Validation + +We recommend using HTML validation like `required` and `type="email"` for basic client-side form validation. + +For more advanced server-side validation, you can use a schema validation library like [zod](https://zod.dev/) to validate the form fields before mutating the data: + +```ts filename="pages/api/submit.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const parsed = schema.parse(req.body) + // ... +} +``` + +```js filename="pages/api/submit.js" switcher +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function handler(req, res) { + const parsed = schema.parse(req.body) + // ... +} +``` + +### Error Handling + +You can use React state to show an error message when a form submission fails: + +```tsx filename="pages/index.tsx" switcher +import React, { useState, FormEvent } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + async function onSubmit(event: FormEvent) { + event.preventDefault() + setIsLoading(true) + setError(null) // Clear previous errors when a new request starts + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + throw new Error('Failed to submit the data. Please try again.') + } + + // Handle response if necessary + const data = await response.json() + // ... + } catch (error) { + // Capture the error message to display to the user + setError(error.message) + console.error(error) + } finally { + setIsLoading(false) + } + } + + return ( +
+ {error &&
{error}
} +
+ + +
+
+ ) +} +``` + +```jsx filename="pages/index.jsx" switcher +import React, { useState } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + async function onSubmit(event) { + event.preventDefault() + setIsLoading(true) + setError(null) // Clear previous errors when a new request starts + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + throw new Error('Failed to submit the data. Please try again.') + } + + // Handle response if necessary + const data = await response.json() + // ... + } catch (error) { + // Capture the error message to display to the user + setError(error.message) + console.error(error) + } finally { + setIsLoading(false) + } + } + + return ( +
+ {error &&
{error}
} +
+ + +
+
+ ) +} +``` + +## Displaying Loading State + +You can use React state to show a loading state when a form is submitting on the server: + +```tsx filename="pages/index.tsx" switcher +import React, { useState, FormEvent } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + + async function onSubmit(event: FormEvent) { + event.preventDefault() + setIsLoading(true) // Set loading to true when the request starts + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // Handle response if necessary + const data = await response.json() + // ... + } catch (error) { + // Handle error if necessary + console.error(error) + } finally { + setIsLoading(false) // Set loading to false when the request completes + } + } + + return ( +
+ + +
+ ) +} +``` + +```jsx filename="pages/index.jsx" switcher +import React, { useState } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + + async function onSubmit(event) { + event.preventDefault() + setIsLoading(true) // Set loading to true when the request starts + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // Handle response if necessary + const data = await response.json() + // ... + } catch (error) { + // Handle error if necessary + console.error(error) + } finally { + setIsLoading(false) // Set loading to false when the request completes + } + } + + return ( +
+ + +
+ ) +} +``` + +### Redirecting + +If you would like to redirect the user to a different route after a mutation, you can [`redirect`](/docs/pages/building-your-application/routing/api-routes#response-helpers) to any absolute or relative URL: + +```ts filename="pages/api/submit.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const id = await addPost() + res.redirect(307, `/post/${id}`) +} +``` + +```js filename="pages/api/submit.js" switcher +export default async function handler(req, res) { + const id = await addPost() + res.redirect(307, `/post/${id}`) +} +``` + +### Setting Cookies + +You can set cookies inside an API Route using the `setHeader` method on the response: + +```ts filename="pages/api/cookie.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') + res.status(200).send('Cookie has been set.') +} +``` + +```js filename="pages/api/cookie.js" switcher +export default async function handler(req, res) { + res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') + res.status(200).send('Cookie has been set.') +} +``` + +### Reading Cookies + +You can read cookies inside an API Route using the [`cookies`](/docs/pages/building-your-application/routing/api-routes#request-helpers) request helper: + +```ts filename="pages/api/cookie.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const auth = req.cookies.authorization + // ... +} +``` + +```js filename="pages/api/cookie.js" switcher +export default async function handler(req, res) { + const auth = req.cookies.authorization + // ... +} +``` + +### Deleting Cookies + +You can delete cookies inside an API Route using the `setHeader` method on the response: + +```ts filename="pages/api/cookie.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') + res.status(200).send('Cookie has been deleted.') +} +``` + +```js filename="pages/api/cookie.js" switcher +export default async function handler(req, res) { + res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') + res.status(200).send('Cookie has been deleted.') +} +``` From ccef54f119e1c00963fb0877b36e6b2940509035 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Thu, 30 Nov 2023 20:12:32 +0000 Subject: [PATCH 17/39] Add tainting section --- .../02-data-fetching/03-patterns.mdx | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx index 62c42029adf98..67f95da57e390 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx @@ -311,3 +311,72 @@ The `utils/get-item` exports can be used by Layouts, Pages, or other components > **Good to know:** > > - We recommend using the [`server-only` package](/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment) to make sure server data fetching functions are never used on the client. + +## Tainting + +We recommend using React's taint APIs, [`taintObjectReference`](https://react.dev/reference/react/experimental_taintObjectReference) and [`taintUniqueValue`](https://react.dev/reference/react/experimental_taintUniqueValue), to prevent whole object instances or sensitive values from being passed to the client. + +To enable tainting in your application, set the Next.js Config [`experimental.taint`](/docs/app/api-reference/next-config-js/experimental#taint) option to `true`: + +```js filename="next.config.js" +module.exports = { + experimental: { + taint: true, + }, +} +``` + +Then pass the object or value you want to taint to the `experimental_taintObjectReference` or `experimental_taintUniqueValue` functions: + +```ts filename="app/actions.ts" switcher +import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react'; + +export async function getUserData(id: string) { + const data = ... + experimental_taintObjectReference( + 'Do not pass the whole user object to the client', + data + ); + experimental_taintUniqueValue( + 'Do not pass the user\'s phone number to the client', + data, + data.phoneNumber + ); + return data; +} +``` + +```js filename="app/actions.js" switcher +import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react'; + +export async function getUserData(id) { + const data = ... + experimental_taintObjectReference( + "Do not pass the whole user object to the client", + data + ); + experimental_taintUniqueValue( + "Do not pass the user's phone number to the client", + data, + data.phoneNumber + ); + return data; +``` + +```tsx filename="app/page.tsx" switcher +import { getUserData } from './data' + +export async function Page({ searchParams }: { searchParams: { id: number } }) { + const userData = getUserData(searchParams.id) + return // error +} +``` + +```jsx filename="app/page.js" switcher +import { getUserData } from './data' + +export async function Page({ searchParams }) { + const userData = getUserData(searchParams.id) + return // error +} +``` From d5af1394f9a8f942b48d1bfd4ab4643a73d42f3b Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Thu, 30 Nov 2023 20:12:49 +0000 Subject: [PATCH 18/39] Fix headings --- .../02-data-fetching/03-patterns.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx index 67f95da57e390..6336d7e41c2ef 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx @@ -6,7 +6,7 @@ description: Learn about common data fetching patterns in React and Next.js. There are a few recommended patterns and best practices for fetching data in React and Next.js. This page will go over some of the most common patterns and how to use them. -### Fetching Data on the Server +## Fetching Data on the Server Whenever possible, we recommend fetching data on the server. This allows you to: @@ -19,7 +19,7 @@ Whenever possible, we recommend fetching data on the server. This allows you to: You can fetch data on the server using Server Components, [Route Handlers](/docs/app/building-your-application/routing/route-handlers), and [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). -### Fetching Data Where It's Needed +## Fetching Data Where It's Needed If you need to use the same data (e.g. current user) in multiple components in a tree, you do not have to fetch data globally, nor forward props between components. Instead, you can use `fetch` or React `cache` in the component that needs the data without worrying about the performance implications of making multiple requests for the same data. @@ -27,7 +27,7 @@ This is possible because `fetch` requests are automatically memoized. Learn more > **Good to know**: This also applies to layouts, since it's not possible to pass data between a parent layout and its children. -### Streaming +## Streaming Streaming and [Suspense](https://react.dev/reference/react/Suspense) are React features that allow you to progressively render and incrementally stream rendered units of the UI to the client. @@ -58,7 +58,7 @@ When fetching data inside React components, you need to be aware of two data fet - With **sequential data fetching**, requests in a route are dependent on each other and therefore create waterfalls. There may be cases where you want this pattern because one fetch depends on the result of the other, or you want a condition to be satisfied before the next fetch to save resources. However, this behavior can also be unintentional and lead to longer loading times. - With **parallel data fetching**, requests in a route are eagerly initiated and will load data at the same time. This reduces client-server waterfalls and the total time it takes to load data. -#### Sequential Data Fetching +### Sequential Data Fetching If you have nested components, and each component fetches its own data, then data fetching will happen sequentially if those data requests are different (this doesn't apply to requests for the same data as they are automatically [memoized](/docs/app/building-your-application/caching#request-memoization)). @@ -140,7 +140,7 @@ This will prevent the whole route from being blocked by data fetching, and the u > > Any fetch requests with `await` will block rendering and data fetching for the entire tree beneath it, unless they are wrapped in a `` boundary or `loading.js` is used. Another alternative is to use [parallel data fetching](#parallel-data-fetching) or the [preload pattern](#preloading-data). -#### Parallel Data Fetching +### Parallel Data Fetching To fetch data in parallel, you can eagerly initiate requests by defining them outside the components that use the data, then calling them from inside the component. This saves time by initiating both requests in parallel, however, the user won't see the rendered result until both promises are resolved. @@ -212,7 +212,7 @@ export default async function Page({ params: { username } }) { To improve the user experience, you can add a [Suspense Boundary](/docs/app/building-your-application/routing/loading-ui-and-streaming) to break up the rendering work and show part of the result as soon as possible. -### Preloading Data +## Preloading Data Another way to prevent waterfalls is to use the preload pattern. You can optionally create a `preload` function to further optimize parallel data fetching. With this approach, you don't have to pass promises down as props. The `preload` function can also have any name as it's a pattern, not an API. From 26954a5a0b12ba97b1d4e8c3fe9aa362d5ead7d6 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Thu, 30 Nov 2023 20:14:04 +0000 Subject: [PATCH 19/39] Add security examples --- .../02-server-actions-and-mutations.mdx | 261 +++--------------- 1 file changed, 35 insertions(+), 226 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 9acc692fa2f15..0bd661db81fb9 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -625,184 +625,65 @@ See [additional examples](/docs/app/api-reference/functions/cookies#deleting-coo ## Security -#### Using React's `taintObjectReference` API +#### Closures and Encryption -For initial load Next.js will run both the Server Components and the Client Components on the server to produce HTML. +Server Actions can be defined as [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures). For example, the `publish` action has access to the `publishVersion` variable in its parent scope: -Server Components (RSC) execute in a separate module system from the Client Components to avoid accidentally exposing information between the two modules. - -Client Components that render through Server-side Rendering (SSR) should be considered as the same security policy as the browser client. It should not gain access to any privileged data or private APIs. It's highly discouraged to use hacks to try to circumvent this protection (such as stashing data on the global object). The principle is that this code should be able to execute the same on the server as the client. In alignment with secure by default practices, Next.js will fail the build if server-only modules are imported from a Client Component. - -Recommend React taint API - general, users should not be relying on the encryption alone - -As we cannot control what data get sent to the client -Encryption is the last resort - for enterprise - -In this approach you'll want to audit your "use client" files carefully. While auditing and reviewing PRs, look at all the exported functions and if the type signature accepts overly broad objects like User, or contains props like token or creditCard. Even privacy sensitive fields like phoneNumber need extra scrutiny. A Client Component should not accept more data than the minimal data it needs to perform its job. - -A Client Component should not accept more data than the minimal data it needs to perform its job. - -```jsx -import Profile from './components/profile.tsx' - -export async function Page({ params: { slug } }) { - const [rows] = await sql`SELECT * FROM user WHERE slug = ${slug}` - const userData = rows[0] - // EXPOSED: This exposes all the fields in userData to the client because - // we are passing the data from the Server Component to the Client. - // This is similar to returning `userData` in `getServerSideProps` - return -} -``` - -```jsx -'use client' -// BAD: This is a bad props interface because it accepts way more data than the -// Client Component needs and it encourages server components to pass all that -// data down. A better solution would be to accept a limited object with just -// the fields necessary for rendering the profile. -export default async function Profile({ user }: { user: User }) { - return ( -
-

{user.name}

- ... -
- ) -} -``` - -Server Only -Code that should only ever execute on the server can be marked with: - -import 'server-only'; -This will cause the build to error if a Client Component tries to import this module. This can be used to ensure that proprietary/sensitive code or internal business logic doesn't accidentally leak to the client. - -The primary way to transfer data is using the React Server Components protocol which happens automatically when passing props to the Client Components. This serialization supports a superset of JSON. Transferring custom classes is not supported and will result in an error. - -Therefore, a nice trick to avoid too large objects being accidentally exposed to the client is to use class for your data access records. +```tsx filename="app/page.tsx" switcher +export default function Page() { + const publishVersion = await getLatestVersion(); -In the upcoming Next.js 14 release, you can also try out the experimental React Taint APIs by enable the taint flag in next.config.js. + async function publish(formData: FormData) { + "use server"; + if (publishVersion !== await getLatestVersion()) { + throw new Error('The version has changed since pressing publish'); + } + ... + } -```js -module.exports = { - experimental: { - taint: true, - }, + return ; } ``` -This lets you mark an object that should not be allowed to be passed to the client as is. - -app/data.ts +```jsx filename="app/page.js" switcher +export default function Page() { + const publishVersion = await getLatestVersion(); -```jsx -import { experimental_taintObjectReference } from 'react'; + async function publish() { + "use server"; + if (publishVersion !== await getLatestVersion()) { + throw new Error('The version has changed since pressing publish'); + } + ... + } -export async function getUserData(id) { - const data = ...; - experimental_taintObjectReference( - 'Do not pass user data to the client', - data - ); - return data; + return ; } ``` -app/page.tsx - -import { getUserData } from './data'; - -export async function Page({ searchParams }) { - const userData = getUserData(searchParams.id); - return ; // error -} -This does not protect against extracting data fields out of this object and passing them along: - -app/page.tsx - -export async function Page({ searchParams }) { - const { name, phone } = getUserData(searchParams.id); - // Intentionally exposing personal data - return ; -} -For unique strings such as tokens, the raw value can be blocked as well using taintUniqueValue. - -app/data.ts - -import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react'; - -export async function getUserData(id) { - const data = ...; - experimental_taintObjectReference( - 'Do not pass user data to the client', - data - ); - experimental_taintUniqueValue( - 'Do not pass tokens to the client', - data, - data.token - ); - return data; -} -However, even this doesn't block derived values. - -It's better to avoid data getting into the Server Components in the first place - using a Data Access Layer. Taint checking provides an additional layer of protection against mistakes by specifying the value, please be mindful that functions and classes are already blocked from being passed to Client Components. More layers the minimize risk of something slipping through. - -By default, environment variables are only available on the Server. By convention, Next.js also exposes any environment variable prefixed with NEXT*PUBLIC* to the client. This lets you expose certain explicit configuration that should be available to the client. - -### Encryption - -Encryption for variables that are sent to the client, user might not be aware of it as it’s automated - -If you define an action inside a server component, - -When you reference variables outside “use server”. +Closures are useful when you need to capture a _snapshot_ of data (e.g. `publishVersion`) at the time of rendering, and use it later when the action is invoked. -That works automatically, when deploying to Vercel and other platforms +However, the captured variables must be sent to the client when the action is invoked. To prevent sensitive data from being exposed, Next.js automatically encrypts all closed-over variables with the action ID. Each time a Next.js application is built (or redeployed), a new private key is generated for the action, meaning each Server Action can only be invoked for a specific build. Encryption works when deploying to Vercel or other platforms. -All this works outside of Vercel +> **Good to know:** We don't recommend relying on encryption alone to prevent sensitive values from being exposed on the client. Instead, you should use the [React taint APIs](/docs/app/building-your-application/data-fetching/patterns#tainting) to prevent specific data from being sent to the client. -Encryption key is changed every time you build your application, meaning redeploying +#### Overwriting Encryption Keys -Prob: If you self host your application in multiple servers, each will get a different key +When self-hosting your application across multiple servers, each server instance may end up with a different encryption key, leading to potential inconsistencies. -process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY - if you specify this variable, you can make sure your keys are identical on each server. +To mitigate this, you can overwrite the encryption key using the `process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` environment variable. Specifying this variable ensures that your encryption keys are persistent across builds and all server instances use the same key. -More of an advanced use case. +This is an advanced use case where consistent encryption behavior across multiple deployments is critical for your application. -By default a private key is generated automatically during the build of a Next.js project. Each rebuild generates a new private key which means that each Server Action can only be invoked for a specific build. You might want to use Skew Protection to ensure that you always invoke the correction version during redeploys. +#### Allowed Origins -If you need a key that rotates more frequently or is persistent across multiple builds, you can configure it manually using NEXT_SERVER_ACTIONS_ENCRYPTION_KEY environment variable. - -By encrypting all closed over variables, you don't accidentally expose any secrets in them. By signing it, it makes it harder for an attacker to mess with the input to the action. - -Another alternative to using closures is to use the .bind(...) function in JavaScript. These are NOT encrypted. This provides an opt-out for performance and is also consistent with .bind() on the client. - -app/page.tsx - -async function deletePost(id: number) { -"use server"; -// verify id and that you can still delete it -... -} - -export async function Page({ slug }) { - const post = await getPost(slug); - return ; -} -The principle is that the argument list to Server Actions ("use server") must always be treated as hostile and the input has to be verified. - -### Built-in CSRF Protection - -All Server Actions can be invoked by plain `
`, which could open them up to [CSRF attacks](https://developer.mozilla.org/en-US/docs/Glossary/CSRF). +All Server Actions can be invoked in a `` element, which could open them up to [CSRF attacks](https://developer.mozilla.org/en-US/docs/Glossary/CSRF). Behind the scenes, Server Actions use the `POST` method, and only this HTTP method is allowed to invoke them. This prevents most CSRF vulnerabilities in modern browsers, particularly with [SameSite cookies](https://web.dev/articles/samesite-cookies-explained) being the default. As an additional protection, Server Actions in Next.js also compares the [Origin header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin) to the [Host header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) (or `X-Forwarded-Host`). If these don't match, the request will be aborted. In other words, Server Actions can only be invoked on the same host as the page that hosts it. -For large applications and organizations that use reverse proxies or multi-layered backend architectures (where the server API differs from the production domain), it's recommended to use the Next Config [`serverActions.allowedOrigins`](/docs/app/api-reference/next-config-js/serverActions) option to specify a list of safe origins. The option accepts an array of strings. +For large applications that use reverse proxies or multi-layered backend architectures (where the server API differs from the production domain), it's recommended to use the Next Config [`serverActions.allowedOrigins`](/docs/app/api-reference/next-config-js/serverActions) option to specify a list of safe origins. The option accepts an array of strings. ```js filename="next.config.js" /** @type {import('next').NextConfig} */ @@ -815,76 +696,4 @@ module.exports = { } ``` -serverActions.allowedOrigins and process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY are more advanced cases - -Planning to have a call with @Delba on the details - here’s a quick outline: - -non-form action calls - -use try-catch to handle errors (or return an error code) - -errors are hidden with a digest - -security - -variables from the closure are encrypted - -all automatic, rotates on new builds, use process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY to override the encryption key (when self-hosting on multiple servers) - -always use the React taint API to prevent accidental leaks. encryption is the last resort - -CSRF protection - next.js ensures the action request is coming from the correct domain - -use serverActions.allowedOrigins to specify safe origins - -link to seb’s security blog post - -New APIs - -Edge Cases / Examples: - -We have examples of calling actions in forms - -But it would be good to add examples of calling actions outside forms - -Called as normal async functions - await, try/catch - -Limitations: The argument to the action call - -Other arguments you pass to the action should be serializable - -But you can return React Elements, what would be a use case for this? - -React will throw the error inside the action again - -Actions triggered in useEffect() or 3rd party libraries. - -For non-user interactions, but will trigger a mutation - -No need to add startTransition in the action call - -Example for useOptimistic API, can - -Triggering actions in a declarative way, state - -Server Actions DX - -Right now, server actions are requests in the network tab, there’s not difference - -Maybe with the dev tools, we can make it easier to debug - -Adding API to middleware, so you can have request.action in your middleware. - -Action name, if the function has it - -Server Actions can be anonymous functions - -https://github.com/vercel/next.js/pull/58023 - -https://github.com/vercel/next.js/pull/58023/files#diff-5c3571605c187caaae3f7f40595b8eff5c9b38fb3130e41f50423469dbd0237b - -https://react.dev/reference/react/useOptimistic - -``` - -``` +Learn more about [Security and Server Actions](https://nextjs.org/blog/security-nextjs-server-components-actions). From eb0d5dc92bb91cc27a8ce0f2b173d098079786e0 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Thu, 30 Nov 2023 20:14:15 +0000 Subject: [PATCH 20/39] Misc --- .../02-server-actions-and-mutations.mdx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 0bd661db81fb9..b81de31cfffd0 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -17,6 +17,7 @@ Server Actions are **asynchronous server functions** that can be invoked from Re - Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple reusable actions per route. The browser also does not refresh on form submission. - The return value of Server Actions must be serializable by React. See the React docs for a list of [serializable arguments and values](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). - Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. + - Server Actions use the `POST` method, and only this HTTP method is allowed to invoke them. - Server Actions inherit the [runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes) from the page or layout they are used on. ## Examples @@ -187,7 +188,7 @@ export default async function submit(formData) { Please refer to the [Zod documentation](https://zod.dev/) for more information. -#### Displaying a Loading State with `useFormStatus` +#### Displaying a form loading state Use the React [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a loading state when a form is submitting on the server. The `useFormStatus` hook can only be used as a child of a `form` element using a Server Action. @@ -253,9 +254,9 @@ export default async function Home() { } ``` -#### Error Handling +#### Error handling in forms -Server Actions can also return [serializable objects](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). For example, your Server Action might handle errors from creating a new item: +Server Actions can also return [serializable objects](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). For example, your Server Action might handle errors from creating a new item by returning a message: ```ts filename="app/actions.ts" switcher 'use server' @@ -432,6 +433,21 @@ export function Thread({ messages }) { } ``` +### Handling Errors + +TODO: Recommend try/catch, errors are hidden with a digest + +Other Edge Cases / Examples: + +- Called as normal async functions - await, try/catch + - Limitations: The argument to the action call + - Other arguments you pass to the action should be serializable + - But you can return React Elements, what would be a use case for this? + - React will throw the error inside the action again + - Actions triggered in useEffect() or 3rd party libraries. For non-user interactions, but will trigger a mutation + - No need to add startTransition in the action call + - Triggering actions in a declarative way? + ### Revalidating Data Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-your-application/caching) on demand. You can invalidate an entire route segment with [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath): From cad4b0f6fb281be65a97cf8e9a160a1430a7c5a7 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Fri, 1 Dec 2023 15:23:31 +0000 Subject: [PATCH 21/39] Review --- .../02-server-actions-and-mutations.mdx | 423 +++++++++--------- .../02-data-fetching/03-patterns.mdx | 51 ++- .../03-environment-variables.mdx | 2 +- .../04-functions/server-actions.mdx | 4 - .../05-next-config-js/serverActions.mdx | 2 +- 5 files changed, 241 insertions(+), 241 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index b81de31cfffd0..835417cde43e7 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -4,34 +4,38 @@ nav_title: Server Actions and Mutations description: Learn how to handle form submissions and data mutations with Next.js. --- -Server Actions are **asynchronous server functions** that can be invoked from React components. They provide a powerful and flexible way to handle form submissions and data mutations in your Next.js applications. +Server Actions are **asynchronous functions** that can be called from the client and are executed on the server. They provide a powerful and flexible way to handle form submissions and data mutations in Next.js applications. + +You can use Server Actions in Server and Client Components. Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. > **🎥 Watch:** Learn more about forms and mutations with the App Router → [YouTube (10 minutes)](https://youtu.be/dDpZfOQBMaU?si=cJZHlUu_jFhCzHUg). -## How Server Actions work +## Behavior -- Server Actions integrate deeply with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server round trip. -- Server Actions can be defined inline in Server Components or invoked from Client Components. - - In Server Components, forms invoking Server Actions support progressive enhancement by default. +- Server actions can be invoked using the `action` attribute in a [`` element](#forms): + - Server Components support progressive enhancement by default, meaning the form will be submitted even if JavaScript hasn't loaded yet or is disabled. - In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. -- Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple reusable actions per route. The browser also does not refresh on form submission. + - The browser does not refresh on form submission. +- Server Actions are not limited to `` elements and can be invoked from event handlers, `useEffect`, and third-party libraries. +- Server Actions integrate with the Next.js [caching and revalidation](/docs/app/building-your-application/caching) architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server round trip. +- Behind the scenes, actions use the `POST` method, and only this HTTP method is allowed to invoke them. - The return value of Server Actions must be serializable by React. See the React docs for a list of [serializable arguments and values](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). -- Since Server Actions execute on the server, they are an alternative to creating [Route Handlers](/docs/app/building-your-application/routing/route-handlers) (API endpoints) to mutate data. - - Server Actions use the `POST` method, and only this HTTP method is allowed to invoke them. +- Server Actions allow multiple reusable actions per route. - Server Actions inherit the [runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes) from the page or layout they are used on. ## Examples -A Server Action can be defined with the React [´"use server"`](https://react.dev/reference/react/use-server) directive. This denotes the function will execute on the server regardless of whether it's invoked from a Server or Client Component. +A Server Action can be defined with the React [´"use server"`](https://react.dev/reference/react/use-server) directive. This denotes the function will execute on the server regardless of whether it's called from the client. ### Server Components In Server Components, the action can be inlined and the `"use server"` added at the top of the function body: ```tsx filename="app/page.tsx" switcher +// Server Component export default function Page() { // Server Action - async function create(formData: FormData) { + async function create() { 'use server' // ... @@ -44,9 +48,10 @@ export default function Page() { ``` ```jsx filename="app/page.jsx" switcher +// Server Component export default function Page() { // Server Action - async function create(formData) { + async function create() { 'use server' // ... @@ -65,7 +70,7 @@ To call a Server Action in a Client Component, create a new file and add the `"u ```tsx filename="app/actions.ts" switcher 'use server' -export async function create(formData: FormData) { +export async function create() { // ... } ``` @@ -73,16 +78,36 @@ export async function create(formData: FormData) { ```js filename="app/actions.js" switcher 'use server' -export async function create(formData) { +export async function create() { // ... } ``` +```tsx filename="app/ui/button.tsx" switcher +import { create } from '@/app/actions' + +export function Button() { + return ( + // ... + ) +} +``` + +```jsx filename="app/ui/button.js" switcher +import { create } from '@/app/actions' + +export function Button() { + return ( + // ... + ) +} +``` + ### Forms React extends the HTML [``](https://developer.mozilla.org/docs/Web/HTML/Element/form) element to allow Server Actions to be invoked with the `action` prop. -When invoked in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object containing the form data. This means you don't have to use React `useState`, instead you can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): +When invoked in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object. This means you don't have to use React `useState` to manage fields, instead you can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): ```tsx filename="app/invoices/page.tsx" switcher export default function Page() { @@ -124,75 +149,17 @@ export default function Page() { > **Good to know:** When working with forms that have many fields, you may want to consider using the [`entries()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries) method with JavaScript's [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries). For example: `const rawFormData = Object.fromEntries(formData.entries())` -To learn more about the `"use server"` directive and the `` element in React, see the following resources: +To learn more about the `` element in React, see the following resources: - [React `` documentation](https://react.dev/reference/react-dom/components/form#handle-form-submission-with-a-server-action) -- [React `use server` documentation](https://react.dev/reference/react/use-server) - Next.js Example: [Form with Loading & Error States](https://github.com/vercel/next.js/tree/canary/examples/next-forms) -#### Form Validation - -We recommend using HTML validation like `required` and `type="email"` for basic client-side form validation. - -For more advanced server-side validation, you can use a schema validation library like [zod](https://zod.dev/) to validate the form fields before mutating the data: - -```tsx filename="app/actions.ts" switcher -import { z } from 'zod' - -const schema = z.object({ - id: z.string({ - invalid_type_error: 'Invalid ID', - }), -}) - -export default async function submit(formData: FormData) { - const validatedFields = schema.safeParse({ - id: formData.get('id'), - }) +#### Pending states - // Return early if the form data is invalid - if (!validatedFields.success) { - return { - errors: validatedFields.error.flatten().fieldErrors, - } - } +You can use the React [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a pending state while the form is being submitted. - // ... -} -``` - -```jsx filename="app/actions.js" switcher -import { z } from 'zod' - -const schema = z.object({ - id: z.string({ - invalid_type_error: 'Invalid ID', - }), -}) - -export default async function submit(formData) { - const validatedFields = schema.safeParse({ - id: formData.get('id'), - }) - - // Return early if the form data is invalid - if (!validatedFields.success) { - return { - errors: validatedFields.error.flatten().fieldErrors, - } - } - - // ... -} -``` - -Please refer to the [Zod documentation](https://zod.dev/) for more information. - -#### Displaying a form loading state - -Use the React [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a loading state when a form is submitting on the server. The `useFormStatus` hook can only be used as a child of a `form` element using a Server Action. - -For example, the following submit button: +- `useFormStatus` returns the status for a specific ``, so it must be defined as a child of the `` element. +- `useFormStatus` is a hook and therefore must be used in a Client Component. ```tsx filename="app/submit-button.tsx" switcher 'use client' @@ -226,14 +193,14 @@ export function SubmitButton() { } ``` -`` can then be used in a form with a Server Action: +`` can then be nested in the form: ```tsx filename="app/page.tsx" switcher import { SubmitButton } from '@/app/submit-button' export default async function Home() { return ( - + @@ -246,7 +213,7 @@ import { SubmitButton } from '@/app/submit-button' export default async function Home() { return ( -
+ @@ -254,105 +221,144 @@ export default async function Home() { } ``` -#### Error handling in forms +#### Form Validation -Server Actions can also return [serializable objects](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values). For example, your Server Action might handle errors from creating a new item by returning a message: +We recommend using HTML validation like `required` and `type="email"` for basic client-side validation. -```ts filename="app/actions.ts" switcher +For more advanced server-side validation, you can use a library like [zod](https://zod.dev/) to validate the form fields before mutating the data: + +```tsx filename="app/actions.ts" switcher +import { z } from 'zod' + +const schema = z.object({ + id: z.string({ + invalid_type_error: 'Invalid ID', + }), +}) + +export default async function submit(formData: FormData) { + const validatedFields = schema.safeParse({ + id: formData.get('id'), + }) + + // Return early if the form data is invalid + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + } + } + + // Mutate data +} +``` + +```jsx filename="app/actions.js" switcher +import { z } from 'zod' + +const schema = z.object({ + id: z.string({ + invalid_type_error: 'Invalid ID', + }), +}) + +export default async function submit(formData) { + const validatedFields = schema.safeParse({ + id: formData.get('id'), + }) + + // Return early if the form data is invalid + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + } + } + + // Mutate data +} +``` + +Please refer to the [Zod documentation](https://zod.dev/) for more information. + +#### Error Handling + +To handle form errors on the server, you can return a serializable object in your action and use the React [`useFormState`](https://react.dev/reference/react-dom/hooks/useFormState) hook to show a message to the user. + +- By passing the action to `useFormState`, the action's function signature changes to receive a new `prevState` or `initialState` parameter as its first argument. +- `useFormState` is a hook and therefore must be used in a Client Component. + +```tsx filename="app/actions.ts" switcher 'use server' -export async function createTodo(prevState: any, formData: FormData) { - try { - await createItem(formData.get('todo')) - return revalidatePath('/') - } catch (e) { - return { message: 'Failed to create' } +export default async function createUser(prevState: any, formData: FormData) { + // ... + return { + message: 'Please enter a valid email', } } ``` -```js filename="app/actions.js" switcher +```jsx filename="app/actions.js" switcher 'use server' -export async function createTodo(prevState, formData) { - try { - await createItem(formData.get('todo')) - return revalidatePath('/') - } catch (e) { - return { message: 'Failed to create' } +export default async function createUser(prevState, formData) { + // ... + return { + message: 'Please enter a valid email', } } ``` -Then, from a Client Component, you can read this value and display an error message. +Then, you can pass your action to the `useFormState` hook and use the returned `state` to display an error message. -```tsx filename="app/add-form.tsx" switcher +```tsx filename="app/ui/signup.tsx" switcher 'use client' -import { useFormState, useFormStatus } from 'react-dom' -import { createTodo } from '@/app/actions' +import { useFormState } from 'react-dom' +import { createUser } from '@/app/actions' const initialState = { message: null, } -function SubmitButton() { - const { pending } = useFormStatus() - - return ( - - ) -} - -export function AddForm() { - const [state, formAction] = useFormState(createTodo, initialState) +export function Signup() { + const [state, formAction] = useFormState(createUser, initialState) return (
- - - + + + {/* ... */}

{state?.message}

+ ) } ``` -```jsx filename="app/add-form.jsx" switcher +```jsx filename="app/ui/signup.js" switcher 'use client' -import { useFormState, useFormStatus } from 'react-dom' -import { createTodo } from '@/app/actions' +import { useFormState } from 'react-dom' +import { createUser } from '@/app/actions' const initialState = { message: null, } -function SubmitButton() { - const { pending } = useFormStatus() - - return ( - - ) -} - -export function AddForm() { - const [state, formAction] = useFormState(createTodo, initialState) +export function Signup() { + const [state, formAction] = useFormState(createUser, initialState) return (
- - - + + + {/* ... */}

{state?.message}

+ ) } @@ -360,7 +366,7 @@ export function AddForm() { #### Optimistic Updates -Use React [`useOptimistic`](https://react.dev/reference/react/useOptimistic) to optimistically update the UI before the Server Action finishes, rather than waiting for the response: +You can use the React [`useOptimistic`](https://react.dev/reference/react/useOptimistic) hook to optimistically update the UI before the Server Action finishes, rather than waiting for the response: ```tsx filename="app/page.tsx" switcher 'use client' @@ -433,31 +439,52 @@ export function Thread({ messages }) { } ``` +Other Edge Cases / Examples: + +- Actions triggered in useEffect() or 3rd party libraries. For non-user interactions, but will trigger a mutation +- No need to add startTransition in the action call +- Triggering actions in a declarative way? + ### Handling Errors -TODO: Recommend try/catch, errors are hidden with a digest +When an error is thrown, it'll be caught by the nearest [error.js](/docs/app/building-your-application/routing/error-handling) boundary on the client. We recommend using `try/catch` to return errors to be handled by your UI. -Other Edge Cases / Examples: +For example, your Server Action might handle errors from creating a new item by returning a message: + +```ts filename="app/actions.ts" switcher +'use server' + +export async function createTodo(prevState: any, formData: FormData) { + try { + await createItem(formData.get('todo')) + } catch (e) { + throw new Error('Failed to create task') + } +} +``` + +```js filename="app/actions.js" switcher +'use server' -- Called as normal async functions - await, try/catch - - Limitations: The argument to the action call - - Other arguments you pass to the action should be serializable - - But you can return React Elements, what would be a use case for this? - - React will throw the error inside the action again - - Actions triggered in useEffect() or 3rd party libraries. For non-user interactions, but will trigger a mutation - - No need to add startTransition in the action call - - Triggering actions in a declarative way? +export async function createTodo(prevState, formData) { + try { + await createItem(formData.get('todo')) + } catch (e) { + throw new Error('Failed to create task') + } +} +``` ### Revalidating Data -Server Actions allow you to invalidate the [Next.js Cache](/docs/app/building-your-application/caching) on demand. You can invalidate an entire route segment with [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath): +You can revalidate the [Next.js Cache](/docs/app/building-your-application/caching) inside your Server Actions with the [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) API: ```ts filename="app/actions.ts" switcher 'use server' import { revalidatePath } from 'next/cache' -export default async function submit() { +export default async function createPost() { try { // ... } catch (error) { @@ -473,7 +500,7 @@ export default async function submit() { import { revalidatePath } from 'next/cache' -export default async function submit() { +export default async function createPost() { try { // ... } catch (error) { @@ -491,7 +518,7 @@ Or invalidate a specific data fetch with a cache tag using [`revalidateTag`](/do import { revalidateTag } from 'next/cache' -export default async function submit() { +export default async function createPost() { try { // ... } catch (error) { @@ -507,7 +534,7 @@ export default async function submit() { import { revalidateTag } from 'next/cache' -export default async function submit() { +export default async function createPost() { try { // ... } catch (error) { @@ -519,7 +546,7 @@ export default async function submit() { ### Redirecting -If you would like to redirect the user to a different route after the completion of a Server Action, you can use [`redirect`](/docs/app/api-reference/functions/redirect) and any absolute or relative URL: +If you would like to redirect the user to a different route after the completion of a Server Action, you can use [`redirect`](/docs/app/api-reference/functions/redirect) API: ```ts filename="app/actions.ts" switcher 'use server' @@ -527,7 +554,7 @@ If you would like to redirect the user to a different route after the completion import { redirect } from 'next/navigation' import { revalidateTag } from 'next/cache' -export default async function submit(id: string) { +export default async function createPost(id: string) { try { // ... } catch (error) { @@ -545,7 +572,7 @@ export default async function submit(id: string) { import { redirect } from 'next/navigation' import { revalidateTag } from 'next/cache' -export default async function submit(id) { +export default async function createPost(id) { try { // ... } catch (error) { @@ -557,46 +584,26 @@ export default async function submit(id) { } ``` -### Cookies +> **Good to know:** `redirect` should be called outside of the `try/catch` block. -#### Setting Cookies +### Cookies -You can set cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: +You can get, set, and delete cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) API: ```ts filename="app/actions.ts" switcher 'use server' import { cookies } from 'next/headers' -export async function create() { - const cart = await createCart() - cookies().set('cartId', cart.id) -} -``` - -```js filename="app/actions.js" switcher -'use server' - -import { cookies } from 'next/headers' - -export async function create() { - const cart = await createCart() - cookies().set('cartId', cart.id) -} -``` +export async function exampleAction() { + // Get cookie + const value = cookies().get('name')?.value -#### Reading Cookies + // Set cookie + cookies().set('name', 123) -You can read cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: - -```ts filename="app/actions.ts" switcher -'use server' - -import { cookies } from 'next/headers' - -export async function read() { - const auth = cookies().get('authorization')?.value - // ... + // Delete cookie + cookies().delete('name') } ``` @@ -605,35 +612,15 @@ export async function read() { import { cookies } from 'next/headers' -export async function read() { - const auth = cookies().get('authorization')?.value - // ... -} -``` - -### Deleting Cookies - -You can delete cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) function: - -```ts filename="app/actions.ts" switcher -'use server' - -import { cookies } from 'next/headers' - -export async function delete() { - cookies().delete('name') - // ... -} -``` - -```js filename="app/actions.js" switcher -'use server' +export async function exampleAction() { + // Get cookie + const value = cookies().get('name')?.value -import { cookies } from 'next/headers' + // Set cookie + cookies().set('name', 123) -export async function delete() { + // Delete cookie cookies().delete('name') - // ... } ``` @@ -643,7 +630,7 @@ See [additional examples](/docs/app/api-reference/functions/cookies#deleting-coo #### Closures and Encryption -Server Actions can be defined as [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures). For example, the `publish` action has access to the `publishVersion` variable in its parent scope: +Defining a Server Action inside a component creates a [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) where the action has access to the outer function's scope. For example, the `publish` action has access to the `publishVersion` variable: ```tsx filename="app/page.tsx" switcher export default function Page() { @@ -677,23 +664,23 @@ export default function Page() { } ``` -Closures are useful when you need to capture a _snapshot_ of data (e.g. `publishVersion`) at the time of rendering, and use it later when the action is invoked. +Closures are useful when you need to capture a _snapshot_ of data (e.g. `publishVersion`) at the time of rendering so that it can be used later when the action is invoked. -However, the captured variables must be sent to the client when the action is invoked. To prevent sensitive data from being exposed, Next.js automatically encrypts all closed-over variables with the action ID. Each time a Next.js application is built (or redeployed), a new private key is generated for the action, meaning each Server Action can only be invoked for a specific build. Encryption works when deploying to Vercel or other platforms. +However, for this to happen, the captured variables are sent to the client and back to the server when the action is invoked. To prevent sensitive data from being exposed on the client, Next.js automatically encrypts the closed-over variables. A new private key is generated for each action every time a Next.js application is built, this means actions can only be invoked for a specific build. -> **Good to know:** We don't recommend relying on encryption alone to prevent sensitive values from being exposed on the client. Instead, you should use the [React taint APIs](/docs/app/building-your-application/data-fetching/patterns#tainting) to prevent specific data from being sent to the client. +> **Good to know:** We don't recommend relying on encryption alone to prevent sensitive values from being exposed on the client. Instead, you should use the [React taint APIs](/docs/app/building-your-application/data-fetching/patterns#tainting) to proactively prevent specific data from being sent to the client. -#### Overwriting Encryption Keys +#### Overwriting Encryption Keys (Advanced) -When self-hosting your application across multiple servers, each server instance may end up with a different encryption key, leading to potential inconsistencies. +Encryption works when deploying to Vercel or other platforms. However, when self-hosting your application across multiple servers, each server instance may end up with a different encryption key, leading to potential inconsistencies. -To mitigate this, you can overwrite the encryption key using the `process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` environment variable. Specifying this variable ensures that your encryption keys are persistent across builds and all server instances use the same key. +To mitigate this, you can overwrite the encryption key using the `process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` environment variable. Specifying this variable ensures that your encryption keys are persistent across builds, and all server instances use the same key. -This is an advanced use case where consistent encryption behavior across multiple deployments is critical for your application. +This is an advanced use case where consistent encryption behavior across multiple deployments is critical for your application. You should consider standard security practices such key rotation and signing. -#### Allowed Origins +#### Allowed Origins (Advanced) -All Server Actions can be invoked in a `
` element, which could open them up to [CSRF attacks](https://developer.mozilla.org/en-US/docs/Glossary/CSRF). +Since Server Actions can be invoked in a `` element, this opens them up to [CSRF attacks](https://developer.mozilla.org/en-US/docs/Glossary/CSRF). Behind the scenes, Server Actions use the `POST` method, and only this HTTP method is allowed to invoke them. This prevents most CSRF vulnerabilities in modern browsers, particularly with [SameSite cookies](https://web.dev/articles/samesite-cookies-explained) being the default. diff --git a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx index 6336d7e41c2ef..16c17e26a54b1 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx @@ -316,7 +316,7 @@ The `utils/get-item` exports can be used by Layouts, Pages, or other components We recommend using React's taint APIs, [`taintObjectReference`](https://react.dev/reference/react/experimental_taintObjectReference) and [`taintUniqueValue`](https://react.dev/reference/react/experimental_taintUniqueValue), to prevent whole object instances or sensitive values from being passed to the client. -To enable tainting in your application, set the Next.js Config [`experimental.taint`](/docs/app/api-reference/next-config-js/experimental#taint) option to `true`: +To enable tainting in your application, set the Next.js Config `experimental.taint` option to `true`: ```js filename="next.config.js" module.exports = { @@ -328,39 +328,46 @@ module.exports = { Then pass the object or value you want to taint to the `experimental_taintObjectReference` or `experimental_taintUniqueValue` functions: -```ts filename="app/actions.ts" switcher -import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react'; +```ts filename="app/utils.ts" switcher +import { + experimental_taintObjectReference, + experimental_taintUniqueValue, +} from 'react' export async function getUserData(id: string) { - const data = ... + const data = {} experimental_taintObjectReference( 'Do not pass the whole user object to the client', data - ); + ) experimental_taintUniqueValue( - 'Do not pass the user\'s phone number to the client', + "Do not pass the user's phone number to the client", data, data.phoneNumber - ); - return data; + ) + return data } ``` -```js filename="app/actions.js" switcher -import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react'; +```js filename="app/utils.js" switcher +import { + experimental_taintObjectReference, + experimental_taintUniqueValue, +} from 'react' export async function getUserData(id) { - const data = ... + const data = {} experimental_taintObjectReference( - "Do not pass the whole user object to the client", + 'Do not pass the whole user object to the client', data - ); + ) experimental_taintUniqueValue( "Do not pass the user's phone number to the client", data, data.phoneNumber - ); - return data; + ) + return data +} ``` ```tsx filename="app/page.tsx" switcher @@ -368,7 +375,12 @@ import { getUserData } from './data' export async function Page({ searchParams }: { searchParams: { id: number } }) { const userData = getUserData(searchParams.id) - return // error + return ( + + ) } ``` @@ -377,6 +389,11 @@ import { getUserData } from './data' export async function Page({ searchParams }) { const userData = getUserData(searchParams.id) - return // error + return ( + + ) } ``` diff --git a/docs/02-app/01-building-your-application/07-configuring/03-environment-variables.mdx b/docs/02-app/01-building-your-application/07-configuring/03-environment-variables.mdx index b19c7573d628f..f57f13f0101d4 100644 --- a/docs/02-app/01-building-your-application/07-configuring/03-environment-variables.mdx +++ b/docs/02-app/01-building-your-application/07-configuring/03-environment-variables.mdx @@ -161,7 +161,7 @@ export default function Component() { - You can run code on server startup using the [`register` function](/docs/app/building-your-application/optimizing/instrumentation). - We do not recommend using the [runtimeConfig](/docs/pages/api-reference/next-config-js/runtime-configuration) option, as this does not work with the standalone output mode. Instead, we recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router. -## Default Environment Variables +## Default Environments In general only one `.env.local` file is needed. However, sometimes you might want to add some defaults for the `development` (`next dev`) or `production` (`next start`) environment. diff --git a/docs/02-app/02-api-reference/04-functions/server-actions.mdx b/docs/02-app/02-api-reference/04-functions/server-actions.mdx index 44c23620a49d2..6a7d49ed7f87d 100644 --- a/docs/02-app/02-api-reference/04-functions/server-actions.mdx +++ b/docs/02-app/02-api-reference/04-functions/server-actions.mdx @@ -9,8 +9,6 @@ related: - app/building-your-application/data-fetching/server-actions-and-mutations --- -{/* TODO: This page will need to link back to React docs mentioned at the bottom */} - Next.js integrates with React Actions to provide a built-in solution for [server mutations](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). ## Convention @@ -37,8 +35,6 @@ export default function ServerComponent() { ### With Client Components -#### Import - Create your Server Action in a separate file with the `"use server"` directive at the top of the file. Then, import the Server Action into your Client Component: ```js filename="app/actions.js" highlight={1} diff --git a/docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx b/docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx index 4d6c565013419..006ffa371808f 100644 --- a/docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx +++ b/docs/02-app/02-api-reference/05-next-config-js/serverActions.mdx @@ -1,6 +1,6 @@ --- title: serverActions -description: x +description: Configure Server Actions behavior in your Next.js application. --- # Options From 9ea54eb68cf671e8c19fa1bfe98e503bc291b7e6 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Fri, 1 Dec 2023 16:03:56 +0000 Subject: [PATCH 22/39] Add bind argument, merge and delete API page --- .../02-server-actions-and-mutations.mdx | 65 ++++++- .../04-functions/server-actions.mdx | 161 ------------------ .../05-next-config-js/serverActions.mdx | 23 ++- 3 files changed, 83 insertions(+), 166 deletions(-) delete mode 100644 docs/02-app/02-api-reference/04-functions/server-actions.mdx diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 835417cde43e7..5734e8182d623 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -16,14 +16,14 @@ You can use Server Actions in Server and Client Components. Since Server Actions - Server Components support progressive enhancement by default, meaning the form will be submitted even if JavaScript hasn't loaded yet or is disabled. - In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration. - The browser does not refresh on form submission. -- Server Actions are not limited to `` elements and can be invoked from event handlers, `useEffect`, and third-party libraries. +- Server Actions are not limited to `` elements and can be invoked from event handlers, `useEffect`, third-party libraries, and other form elements like ` + + ) +} +``` + +`updateUser` Server Action will always receive the `userId` argument, in addition to the form data: + +```js filename="app/actions.js" +'use server' + +export async function updateUser(userId, formData) { + // ... +} +``` + +> **Good to know**: `.bind` of a Server Action works in both Server and Client Components. It also supports [Progressive Enhancement](#progressive-enhancement). + +### Error Handling When an error is thrown, it'll be caught by the nearest [error.js](/docs/app/building-your-application/routing/error-handling) boundary on the client. We recommend using `try/catch` to return errors to be handled by your UI. @@ -700,3 +749,13 @@ module.exports = { ``` Learn more about [Security and Server Actions](https://nextjs.org/blog/security-nextjs-server-components-actions). + +## Additional Resources + +For more information on Server Actions, check out the following React docs: + +- [`"use server"`](https://react.dev/reference/react/use-server) +- [`
`](https://react.dev/reference/react-dom/components/form) +- [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) +- [`useFormState`](https://react.dev/reference/react-dom/hooks/useFormState) +- [`useOptimistic`](https://react.dev/reference/react/useOptimistic) diff --git a/docs/02-app/02-api-reference/04-functions/server-actions.mdx b/docs/02-app/02-api-reference/04-functions/server-actions.mdx deleted file mode 100644 index 6a7d49ed7f87d..0000000000000 --- a/docs/02-app/02-api-reference/04-functions/server-actions.mdx +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: Server Actions -nav_title: Server Actions -description: API Reference for Next.js Server Actions. -related: - title: Next Steps - description: For more information on what to do next, we recommend the following sections - links: - - app/building-your-application/data-fetching/server-actions-and-mutations ---- - -Next.js integrates with React Actions to provide a built-in solution for [server mutations](/docs/app/building-your-application/data-fetching/server-actions-and-mutations). - -## Convention - -Server Actions can be defined in two places: - -- Inside the component that uses it (Server Components only). -- In a separate file (Client and Server Components), for reusability. You can define multiple Server Actions in a single file. - -### With Server Components - -Create a Server Action by defining an asynchronous function with the [`"use server"`](https://react.dev/reference/react/use-server) directive at the top of the function body. `"use server"` ensures this function is only ever executed on the server. - -This function should have [serializable arguments](https://developer.mozilla.org/docs/Glossary/Serialization) and a [serializable return value](https://developer.mozilla.org/docs/Glossary/Serialization). - -```jsx filename="app/page.js" highlight={2} -export default function ServerComponent() { - async function myAction() { - 'use server' - // ... - } -} -``` - -### With Client Components - -Create your Server Action in a separate file with the `"use server"` directive at the top of the file. Then, import the Server Action into your Client Component: - -```js filename="app/actions.js" highlight={1} -'use server' - -export async function myAction() { - // ... -} -``` - -```jsx filename="app/client-component.jsx" highlight={1} -'use client' - -import { myAction } from './actions' - -export default function ClientComponent() { - return ( - - -
- ) -} -``` - -> **Good to know**: When using a top-level `"use server"` directive, all exports below will be considered Server Actions. You can have multiple Server Actions in a single file. - -#### Props - -In some cases, you might want to pass down a Server Action to a Client Component as a prop. - -```jsx - -``` - -```jsx filename="app/client-component.jsx" -'use client' - -export default function ClientComponent({ updateItem }) { - return ( -
- - -
- ) -} -``` - -### Binding Arguments - -You can bind arguments to a Server Action using the `bind` method. This allows you to create a new Server Action with some arguments already bound. This is beneficial when you want to pass extra arguments to a Server Action. - -```jsx filename="app/client-component.jsx" highlight={6} -'use client' - -import { updateUser } from './actions' - -export function UserProfile({ userId }) { - const updateUserWithId = updateUser.bind(null, userId) - - return ( -
- - -
- ) -} -``` - -And then, the `updateUser` Server Action will always receive the `userId` argument, in addition to the form data: - -```js filename="app/actions.js" -'use server' - -export async function updateUser(userId, formData) { - // ... -} -``` - -> **Good to know**: `.bind` of a Server Action works in both Server and Client Components. It also supports [Progressive Enhancement](#progressive-enhancement). - -## Invocation - -You can invoke Server Actions using the following methods: - -- Using `action`: React's `action` prop allows invoking a Server Action on a `
` element. -- Using `formAction`: React's `formAction` prop allows handling ` +
+ ) +} +``` + +`updateUser` Server Action will always receive the `userId` argument, in addition to the form data: + +```js filename="app/actions.js" +'use server' + +export async function updateUser(userId, formData) { + // ... +} +``` + +> **Good to know**: `.bind` of a Server Action works in both Server and Client Components. It also supports [Progressive Enhancement](#progressive-enhancement). + #### Pending states You can use the React [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a pending state while the form is being submitted. @@ -455,45 +488,72 @@ export function Thread({ messages }) { } ``` -Other Edge Cases / Examples: +### Non-form Elements -- Actions triggered in useEffect() or 3rd party libraries. For non-user interactions, but will trigger a mutation -- No need to add startTransition in the action call -- Triggering actions in a declarative way? +While it's common to use Server Actions with `
` elements, they can also be invoked from other parts of your code such as event handlers and `useEffect`. -### Binding Additional Arguments +#### Event Handlers -You can bind additional arguments to a Server Action using the JavaScript `bind` method. +```js filename="app/actions.js" switcher +'use server' -```jsx filename="app/client-component.jsx" highlight={6} +export default async function incrementLike() { + // Mutate database + // Return updated data +} +``` + +```tsx filename="app/like-button.tsx" switcher 'use client' -import { updateUser } from './actions' +import { incrementLike } from './actions' +import { useState } from 'react' -export function UserProfile({ userId }) { - const updateUserWithId = updateUser.bind(null, userId) +export default function LikeButton({ initialLikes }: { initialLikes: number }) { + const [likes, setLikes] = useState(initialLikes) return ( - - - -
+ <> +

Total Likes: {likes}

+ + ) } ``` -`updateUser` Server Action will always receive the `userId` argument, in addition to the form data: +> **Good to know:** To improve the user experience, we recommend using other React APIs like [`useOptimistic`](https://react.dev/reference/react/useOptimistic) and [`useTransition`](https://react.dev/reference/react/useTransition) to update the UI before the Server Action finishes or show a pending state. -```js filename="app/actions.js" -'use server' +#### `useEffect` -export async function updateUser(userId, formData) { - // ... +```tsx filename="app/view-count.tsx" switcher +'use client' + +import { incrementViews } from './actions' +import { useState, useEffect } from 'react' + +export default function ViewCount({ initialViews }: { initialViews: number }) { + const [views, setViews] = useState(initialViews) + + useEffect(() => { + const updateViews = async () => { + const updatedViews = await incrementViews() + setViews(updatedViews) + } + + updateViews() + }, []) + + return

Total Views: {views}

} ``` -> **Good to know**: `.bind` of a Server Action works in both Server and Client Components. It also supports [Progressive Enhancement](#progressive-enhancement). - ### Error Handling When an error is thrown, it'll be caught by the nearest [error.js](/docs/app/building-your-application/routing/error-handling) boundary on the client. We recommend using `try/catch` to return errors to be handled by your UI. From 67ba1fd0252a7d24164a084f83f2c70a12986f0a Mon Sep 17 00:00:00 2001 From: Delba de Oliveira Date: Mon, 4 Dec 2023 13:27:58 +0000 Subject: [PATCH 24/39] Add section descriptions --- .../02-server-actions-and-mutations.mdx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 1b865909b82cc..232717d434822 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -488,12 +488,18 @@ export function Thread({ messages }) { } ``` +#### `