Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename serverActionsSizeLimit as serverActionsBodySizeLimit and add docs #51755

Merged
merged 2 commits into from Jun 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -382,6 +382,21 @@ export default function ExampleClientComponent({ myAction }) {

In both cases, the form is interactive before hydration occurs. Although Server Actions have the additional benefit of not relying on client JavaScript, you can still compose additional behavior with Client Actions where desired without sacrificing interactivity.

### Size Limitation

By default, the maximum size of the request body sent to an Server Action is 1MB. This prevents large amounts of data being sent to the server, which consumes a lot of server resource to parse.

However, you can configure this limit using the **experimental** `serverActionsBodySizeLimit` option. It can take the number of bytes or any string format supported by bytes, for example `1000`, `'500kb'` or `'3mb'`.

```js filename="next.config.js"
module.exports = {
experimental: {
serverActions: true,
serverActionsBodySizeLimit: '2mb',
},
}
```

## Examples

### Usage with Client Components
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/build/entries.ts
Expand Up @@ -377,7 +377,8 @@ export function getEdgeServerEntry(opts: {
middlewareConfig: Buffer.from(
JSON.stringify(opts.middlewareConfig || {})
).toString('base64'),
serverActionsSizeLimit: opts.config.experimental.serverActionsSizeLimit,
serverActionsBodySizeLimit:
opts.config.experimental.serverActionsBodySizeLimit,
}

return {
Expand Down
Expand Up @@ -23,7 +23,7 @@ export type EdgeSSRLoaderQuery = {
incrementalCacheHandlerPath?: string
preferredRegion: string | string[] | undefined
middlewareConfig: string
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
}

/*
Expand Down Expand Up @@ -57,7 +57,7 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
incrementalCacheHandlerPath,
preferredRegion,
middlewareConfig: middlewareConfigBase64,
serverActionsSizeLimit,
serverActionsBodySizeLimit,
} = this.getOptions()

const middlewareConfig: MiddlewareConfig = JSON.parse(
Expand Down Expand Up @@ -177,10 +177,10 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
reactLoadableManifest,
clientReferenceManifest: ${isServerComponent} ? rscManifest : null,
serverActionsManifest: ${isServerComponent} ? rscServerManifest : null,
serverActionsSizeLimit: ${isServerComponent} ? ${
typeof serverActionsSizeLimit === 'undefined'
serverActionsBodySizeLimit: ${isServerComponent} ? ${
typeof serverActionsBodySizeLimit === 'undefined'
? 'undefined'
: JSON.stringify(serverActionsSizeLimit)
: JSON.stringify(serverActionsBodySizeLimit)
} : undefined,
subresourceIntegrityManifest,
config: ${stringifiedConfig},
Expand Down
Expand Up @@ -32,7 +32,7 @@ export function getRender({
clientReferenceManifest,
subresourceIntegrityManifest,
serverActionsManifest,
serverActionsSizeLimit,
serverActionsBodySizeLimit,
config,
buildId,
nextFontManifest,
Expand All @@ -53,7 +53,7 @@ export function getRender({
subresourceIntegrityManifest?: Record<string, string>
clientReferenceManifest?: ClientReferenceManifest
serverActionsManifest: any
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
appServerMod: any
config: NextConfigComplete
buildId: string
Expand Down Expand Up @@ -88,7 +88,7 @@ export function getRender({
disableOptimizedLoading: true,
clientReferenceManifest,
serverActionsManifest,
serverActionsSizeLimit,
serverActionsBodySizeLimit,
},
renderToHTML,
incrementalCacheHandler,
Expand Down
Expand Up @@ -37,7 +37,7 @@ interface Options {
appDir: string
isEdgeServer: boolean
useServerActions: boolean
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
}

const PLUGIN_NAME = 'ClientEntryPlugin'
Expand Down Expand Up @@ -153,15 +153,15 @@ export class ClientReferenceEntryPlugin {
appDir: string
isEdgeServer: boolean
useServerActions: boolean
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
assetPrefix: string

constructor(options: Options) {
this.dev = options.dev
this.appDir = options.appDir
this.isEdgeServer = options.isEdgeServer
this.useServerActions = options.useServerActions
this.serverActionsSizeLimit = options.serverActionsSizeLimit
this.serverActionsBodySizeLimit = options.serverActionsBodySizeLimit
this.assetPrefix = !this.dev && !this.isEdgeServer ? '../' : ''
}

Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/export/index.ts
Expand Up @@ -472,7 +472,8 @@ export default async function exportApp(
largePageDataBytes: nextConfig.experimental.largePageDataBytes,
serverComponents: options.hasAppDir,
hasServerComponents: options.hasAppDir,
serverActionsSizeLimit: nextConfig.experimental.serverActionsSizeLimit,
serverActionsBodySizeLimit:
nextConfig.experimental.serverActionsBodySizeLimit,
nextFontManifest: require(join(
distDir,
'server',
Expand Down
21 changes: 17 additions & 4 deletions packages/next/src/server/app-render/action-handler.ts
Expand Up @@ -6,6 +6,7 @@ import type {
} from 'http'
import type { WebNextRequest } from '../base-http/web'
import type { SizeLimit } from '../../../types'
import type { ApiError } from '../api-utils'

import {
ACTION,
Expand Down Expand Up @@ -246,7 +247,7 @@ export async function handleAction({
generateFlight,
staticGenerationStore,
requestStore,
serverActionsSizeLimit,
serverActionsBodySizeLimit,
}: {
req: IncomingMessage
res: ServerResponse
Expand All @@ -260,7 +261,7 @@ export async function handleAction({
}) => Promise<RenderResult>
staticGenerationStore: StaticGenerationStore
requestStore: RequestStore
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
}): Promise<undefined | RenderResult | 'not-found'> {
let actionId = req.headers[ACTION.toLowerCase()] as string
const contentType = req.headers['content-type']
Expand Down Expand Up @@ -376,8 +377,20 @@ export async function handleAction({
const { parseBody } =
require('../api-utils/node') as typeof import('../api-utils/node')

const actionData =
(await parseBody(req, serverActionsSizeLimit ?? '1mb')) || ''
let actionData
try {
actionData =
(await parseBody(req, serverActionsBodySizeLimit ?? '1mb')) ||
''
} catch (e: any) {
if (e && (e as ApiError).statusCode === 413) {
// Exceeded the size limit
e.message =
e.message +
'\nTo configure the body size limit for Server Actions, see: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions#size-limitation'
}
throw e
}

if (isURLEncodedAction) {
const formData = formDataFromSearchQueryString(actionData)
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/server/app-render/app-render.tsx
Expand Up @@ -156,7 +156,7 @@ export async function renderToHTMLOrFlight(
nextFontManifest,
supportsDynamicHTML,
nextConfigOutput,
serverActionsSizeLimit,
serverActionsBodySizeLimit,
} = renderOpts

const appUsingSizeAdjust = nextFontManifest?.appUsingSizeAdjust
Expand Down Expand Up @@ -1607,7 +1607,7 @@ export async function renderToHTMLOrFlight(
generateFlight,
staticGenerationStore,
requestStore,
serverActionsSizeLimit,
serverActionsBodySizeLimit,
})

if (actionRequestResult === 'not-found') {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/app-render/types.ts
Expand Up @@ -146,7 +146,7 @@ export type RenderOptsPartial = {
rawConfig?: boolean,
silent?: boolean
) => Promise<NextConfigComplete>
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
}

export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
6 changes: 3 additions & 3 deletions packages/next/src/server/base-server.ts
Expand Up @@ -255,7 +255,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
supportsDynamicHTML?: boolean
isBot?: boolean
clientReferenceManifest?: ClientReferenceManifest
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
serverActionsManifest?: any
nextFontManifest?: NextFontManifest
renderServerComponentData?: boolean
Expand Down Expand Up @@ -1651,8 +1651,8 @@ export default abstract class Server<ServerOptions extends Options = Options> {
incrementalCache,
isRevalidate: isSSG,
originalPathname: components.ComponentMod.originalPathname,
serverActionsSizeLimit:
this.nextConfig.experimental.serverActionsSizeLimit,
serverActionsBodySizeLimit:
this.nextConfig.experimental.serverActionsBodySizeLimit,
}
: {}),
isDataReq,
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/config-schema.ts
Expand Up @@ -304,7 +304,7 @@ const configSchema = {
serverActions: {
type: 'boolean',
},
serverActionsSizeLimit: {
serverActionsBodySizeLimit: {
oneOf: [
{
type: 'number',
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/config-shared.ts
Expand Up @@ -286,7 +286,7 @@ export interface ExperimentalConfig {
/**
* Allows adjusting body parser size limit for server actions.
*/
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
}

export type ExportPathMap = {
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/server/config.ts
Expand Up @@ -416,9 +416,9 @@ function assignDefaults(
result.output = 'standalone'
}

if (typeof result.experimental?.serverActionsSizeLimit !== 'undefined') {
if (typeof result.experimental?.serverActionsBodySizeLimit !== 'undefined') {
const value = parseInt(
result.experimental.serverActionsSizeLimit.toString()
result.experimental.serverActionsBodySizeLimit.toString()
)
if (isNaN(value) || value < 1) {
throw new Error(
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/render.tsx
Expand Up @@ -261,7 +261,7 @@ export type RenderOptsPartial = {
isBot?: boolean
runtime?: ServerRuntime
serverComponents?: boolean
serverActionsSizeLimit?: SizeLimit
serverActionsBodySizeLimit?: SizeLimit
customServer?: boolean
crossOrigin?: 'anonymous' | 'use-credentials' | '' | undefined
images: ImageConfigComplete
Expand Down
24 changes: 13 additions & 11 deletions test/e2e/app-dir/actions/app-action-size-limit-invalid.test.ts
Expand Up @@ -21,14 +21,14 @@ createNextDescribe(
return
}

it('should error if serverActionsSizeLimit config is a negative number', async function () {
it('should error if serverActionsBodySizeLimit config is a negative number', async function () {
await next.patchFile(
'next.config.js',
`
module.exports = {
experimental: {
serverActions: true,
serverActionsSizeLimit: -3000,
serverActionsBodySizeLimit: -3000,
},
}
`
Expand All @@ -40,14 +40,14 @@ createNextDescribe(
expect(next.cliOutput).toContain(CONFIG_ERROR)
})

it('should error if serverActionsSizeLimit config is invalid', async function () {
it('should error if serverActionsBodySizeLimit config is invalid', async function () {
await next.patchFile(
'next.config.js',
`
module.exports = {
experimental: {
serverActions: true,
serverActionsSizeLimit: 'testmb',
serverActionsBodySizeLimit: 'testmb',
},
}
`
Expand All @@ -59,14 +59,14 @@ createNextDescribe(
expect(next.cliOutput).toContain(CONFIG_ERROR)
})

it('should error if serverActionsSizeLimit config is a negative size', async function () {
it('should error if serverActionsBodySizeLimit config is a negative size', async function () {
await next.patchFile(
'next.config.js',
`
module.exports = {
experimental: {
serverActions: true,
serverActionsSizeLimit: '-3000mb',
serverActionsBodySizeLimit: '-3000mb',
},
}
`
Expand All @@ -79,14 +79,14 @@ createNextDescribe(
})

if (!isNextDeploy) {
it('should respect the size set in serverActionsSizeLimit', async function () {
it('should respect the size set in serverActionsBodySizeLimit', async function () {
await next.patchFile(
'next.config.js',
`
module.exports = {
experimental: {
serverActions: true,
serverActionsSizeLimit: '1.5mb',
serverActionsBodySizeLimit: '1.5mb',
},
}
`
Expand All @@ -112,9 +112,11 @@ createNextDescribe(
await browser.elementByCss('#size-2mb').click()

await check(() => {
return logs.some((log) =>
log.includes('Error: Body exceeded 1.5mb limit')
)
const fullLog = logs.join('')
return fullLog.includes('Error: Body exceeded 1.5mb limit') &&
fullLog.includes(
'To configure the body size limit for Server Actions, see'
)
? 'yes'
: ''
}, 'yes')
Expand Down