-
Notifications
You must be signed in to change notification settings - Fork 210
feat: support experimental.serverActions.bodySizeLimit from next.config #338
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,43 @@ import fs from "node:fs"; | |
| import { PHASE_DEVELOPMENT_SERVER } from "../shims/constants.js"; | ||
| import { normalizePageExtensions } from "../routing/file-matcher.js"; | ||
|
|
||
| /** | ||
| * Parse a body size limit value (string or number) into bytes. | ||
| * Accepts Next.js-style strings like "1mb", "500kb", "10mb", bare number strings like "1048576" (bytes), | ||
| * and numeric values. Supports b, kb, mb, gb, tb, pb units. | ||
| * Returns the default 1MB if the value is not provided or invalid. | ||
| * Throws if the parsed value is less than 1. | ||
| */ | ||
| export function parseBodySizeLimit(value: string | number | undefined | null): number { | ||
| if (value === undefined || value === null) return 1 * 1024 * 1024; | ||
| if (typeof value === "number") { | ||
| if (value < 1) throw new Error(`Body size limit must be a positive number, got ${value}`); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor inconsistency: numeric |
||
| return value; | ||
| } | ||
| const trimmed = value.trim(); | ||
| const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb|tb|pb)?$/i); | ||
| if (!match) { | ||
| console.warn( | ||
| `[vinext] Invalid bodySizeLimit value: "${value}". Expected a number or a string like "1mb", "500kb". Falling back to 1MB.`, | ||
| ); | ||
| return 1 * 1024 * 1024; | ||
| } | ||
| const num = parseFloat(match[1]); | ||
| const unit = (match[2] ?? "b").toLowerCase(); | ||
| let bytes: number; | ||
| switch (unit) { | ||
| case "b": bytes = Math.floor(num); break; | ||
| case "kb": bytes = Math.floor(num * 1024); break; | ||
| case "mb": bytes = Math.floor(num * 1024 * 1024); break; | ||
| case "gb": bytes = Math.floor(num * 1024 * 1024 * 1024); break; | ||
| case "tb": bytes = Math.floor(num * 1024 * 1024 * 1024 * 1024); break; | ||
| case "pb": bytes = Math.floor(num * 1024 * 1024 * 1024 * 1024 * 1024); break; | ||
| default: return 1 * 1024 * 1024; | ||
| } | ||
| if (bytes < 1) throw new Error(`Body size limit must be a positive number, got ${bytes}`); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This The string
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: the expect(() => parseBodySizeLimit("0b")).toThrow();
expect(() => parseBodySizeLimit("0mb")).toThrow();Not blocking. |
||
| return bytes; | ||
| } | ||
|
|
||
| export interface HasCondition { | ||
| type: "header" | "cookie" | "query" | "host"; | ||
| key: string; | ||
|
|
@@ -169,6 +206,8 @@ export interface ResolvedNextConfig { | |
| allowedDevOrigins: string[]; | ||
| /** Extra allowed origins for server action CSRF validation (from experimental.serverActions.allowedOrigins). */ | ||
| serverActionsAllowedOrigins: string[]; | ||
| /** Parsed body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). Defaults to 1MB. */ | ||
| serverActionsBodySizeLimit: number; | ||
| } | ||
|
|
||
| const CONFIG_FILES = [ | ||
|
|
@@ -286,6 +325,7 @@ export async function resolveNextConfig( | |
| aliases: {}, | ||
| allowedDevOrigins: [], | ||
| serverActionsAllowedOrigins: [], | ||
| serverActionsBodySizeLimit: 1 * 1024 * 1024, | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -334,7 +374,7 @@ export async function resolveNextConfig( | |
| ? config.allowedDevOrigins | ||
| : []; | ||
|
|
||
| // Resolve serverActions.allowedOrigins from experimental config | ||
| // Resolve serverActions.allowedOrigins and bodySizeLimit from experimental config | ||
| const experimental = config.experimental as Record<string, unknown> | undefined; | ||
| const serverActionsConfig = experimental?.serverActions as | ||
| | Record<string, unknown> | ||
|
|
@@ -344,6 +384,7 @@ export async function resolveNextConfig( | |
| ) | ||
| ? (serverActionsConfig.allowedOrigins as string[]) | ||
| : []; | ||
| const serverActionsBodySizeLimit = parseBodySizeLimit(serverActionsConfig?.bodySizeLimit as string | number | undefined); | ||
|
|
||
| // Warn about unsupported webpack usage. We preserve alias injection and | ||
| // extract MDX settings, but all other webpack customization is still ignored. | ||
|
|
@@ -394,6 +435,7 @@ export async function resolveNextConfig( | |
| aliases, | ||
| allowedDevOrigins, | ||
| serverActionsAllowedOrigins, | ||
| serverActionsBodySizeLimit, | ||
| }; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,8 @@ export interface AppRouterConfig { | |
| allowedOrigins?: string[]; | ||
| /** Extra origins allowed for dev server access (from allowedDevOrigins). */ | ||
| allowedDevOrigins?: string[]; | ||
| /** Body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). */ | ||
| bodySizeLimit?: number; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -57,6 +59,7 @@ export function generateRscEntry( | |
| const rewrites = config?.rewrites ?? { beforeFiles: [], afterFiles: [], fallback: [] }; | ||
| const headers = config?.headers ?? []; | ||
| const allowedOrigins = config?.allowedOrigins ?? []; | ||
| const bodySizeLimit = config?.bodySizeLimit ?? 1 * 1024 * 1024; | ||
| // Build import map for all page and layout files | ||
| const imports: string[] = []; | ||
| const importMap: Map<string, string> = new Map(); | ||
|
|
@@ -1290,12 +1293,13 @@ function __isExternalUrl(url) { | |
| } | ||
|
|
||
| /** | ||
| * Maximum server-action request body size (1 MB). | ||
| * Matches the Next.js default for serverActions.bodySizeLimit. | ||
| * Maximum server-action request body size. | ||
| * Configurable via experimental.serverActions.bodySizeLimit in next.config. | ||
| * Defaults to 1MB, matching the Next.js default. | ||
| * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/serverActions#bodysizelimit | ||
| * Prevents unbounded request body buffering. | ||
| */ | ||
| var __MAX_ACTION_BODY_SIZE = 1 * 1024 * 1024; | ||
| var __MAX_ACTION_BODY_SIZE = ${JSON.stringify(bodySizeLimit)}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| /** | ||
| * Read a request body as text with a size limit. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Next.js validates that the parsed value is
>= 1and throws an error for negative numbers or zero (seepackages/next/src/server/config.ts). This implementation silently accepts0, negative numbers, and"0mb"as valid. Consider adding validation:or even throwing, to match Next.js behavior:
Not a blocker, but something to consider for parity.