From d3a4b1852582e5dda5abdb29288ef8257c3c6941 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:00:11 +0800 Subject: [PATCH] build(docs): stop emitting the 348 MB of server source maps the OOM-killed build was paying for Every objectstack.ai production deploy on 2026-08-27 died with exit 137 and Vercel's `errorCode: "out_of_memory"` on a 4-core/8192 MB build machine. The build now declares `experimental.turbopackSourceMaps: false`, suppressing 260 map files totalling 348 MB that no production serverless function reads. The knob matters because of where the kill lands: every failing log places it between `Creating an optimized production build ...` and `Compiled successfully` with no output in between, i.e. inside the Turbopack compile phase. Neither knob already present reaches there -- `experimental.cpus` bounds static-generation workers that have not spawned yet, and `--max-old-space-size` bounds V8 while Turbopack allocates from Rust outside it. Measured, local cold build of all 403 pages, peak single-process RSS: 5191 MB -> 4757 MB (-434 MB, -8.4%); compile 22.6s -> 19.5s. Three other candidates measured as noise and are retired: turbopackScopeHoisting (4806 MB), --max-old-space-size=2048 (4734 MB), turbopackFileSystemCacheForBuild (4779 MB). Not paired with `turbopackMinify: false`, which takes a further 972 MB off the peak but inflates client JS from 5.8 MB to 16 MB (+176%). Confining minification to the server side is not available: `experimental.serverMinification` is read only by the webpack config, never on the Turbopack path. Buys margin; does not prove the ceiling is cleared. The measurement is macOS/arm64 against a Linux/x86_64 container where the same phase measured 7.6 GB (#12683), leaving roughly 15% headroom. #12683's option A (larger build machine) is unaffected and remains the answer if the next build still dies. Refs #12711, #12683, #12698 Co-Authored-By: Claude Opus 5 --- .../docs-build-suppress-server-sourcemaps.md | 55 +++++++++++++++++++ apps/docs/next.config.mjs | 29 ++++++++++ 2 files changed, 84 insertions(+) create mode 100644 .changeset/docs-build-suppress-server-sourcemaps.md diff --git a/.changeset/docs-build-suppress-server-sourcemaps.md b/.changeset/docs-build-suppress-server-sourcemaps.md new file mode 100644 index 0000000000..58c7047995 --- /dev/null +++ b/.changeset/docs-build-suppress-server-sourcemaps.md @@ -0,0 +1,55 @@ +--- +"@objectstack/docs": patch +--- + +build(docs): stop emitting the 348 MB of server source maps the OOM-killed build was paying for (#12711) + +Every `objectstack.ai` production deploy on 2026-08-27 died with `exit 137` and +Vercel's `errorCode: "out_of_memory"` on a 4-core/8192 MB build machine. The +build now declares `experimental.turbopackSourceMaps: false`, which suppresses +260 map files totalling 348 MB that no production serverless function reads. + +The knob matters because of **where** the kill lands. Every failing log places it +between `Creating an optimized production build ...` and `Compiled successfully`, +with no output in between — inside the Turbopack compile phase, which the two +knobs already present cannot reach: + +- `experimental.cpus: 2` bounds static-generation workers that have not spawned + yet when the process dies. +- `NODE_OPTIONS=--max-old-space-size` bounds V8's old space, while Turbopack + allocates from Rust outside it. + +Measured on a local cold build of all 403 pages, as peak single-process RSS: + +| config | peak | compile | +|---|---|---| +| before | 5191 MB | 22.6s | +| **`turbopackSourceMaps: false`** | **4757 MB** | **19.5s** | +| `turbopackScopeHoisting: false` | 4806 MB | 19.1s | +| `--max-old-space-size=2048` | 4734 MB | 18.0s | +| `turbopackFileSystemCacheForBuild: true` | 4779 MB | 22.2s | + +Only the first row moves anything; the rest are noise, which is the measurement +that retires them as candidates rather than leaving them to be re-tried. + +Set explicitly on purpose. Next documents this flag's build-time default as +following `productionBrowserSourceMaps` (false), but the server-side maps are +emitted regardless — naming it is what suppresses them. + +Deliberately not paired with `turbopackMinify: false`, which takes a further +972 MB off the peak (3785 MB) and 3s off the compile: it inflates client JS from +5.8 MB to 16 MB (+176%), a cost every reader pays on every visit to save memory +in a machine they never touch. Confining minification to the server side — where +the memory actually goes, 589 MB of server chunks against 5.8 MB of client — is +not available: `experimental.serverMinification` is read only by +`dist/build/webpack-config.js`, never on the Turbopack path. + +No output change beyond the absent maps: same routes, same 1221 prerendered +paths, same rendered bytes. + +**This buys margin; it does not prove the ceiling is cleared.** The measurement +above is macOS/arm64 and the build container is Linux/x86_64, where the same +phase was measured at 7.6 GB (#12683) against local 4.7 GB — the ratio is the +transferable part, and −8.4% onto 7592 MB leaves roughly 15% headroom, which is +thin. #12683's option A (a larger build machine) is unaffected by this change and +remains the answer if the next production build still dies. diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 25f153c8e0..05b9429a7d 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -49,6 +49,35 @@ const config = { // multiplied the resident set until the build was OOM-killed (exit 137). // Cap the worker count so peak memory stays well under the container limit. cpus: 2, + // Suppress server-side source maps. Measured during the 2026-08-27 production + // outage (#12711): the build emitted 260 map files totalling 348 MB into + // `.next/server`, and nothing in a production serverless function reads them. + // + // This is a memory knob, not a disk one, and it is the only free one that + // reaches the phase that actually dies. Those builds were SIGKILLed (exit 137, + // Vercel `errorCode: "out_of_memory"`) on a 4-core/8192 MB build machine, and + // the logs place every kill between `Creating an optimized production build` + // and `Compiled successfully`, with zero output in between -- inside the + // Turbopack COMPILE phase. Two knobs that look like they should help cannot: + // `cpus` above bounds static-generation workers that have not spawned yet when + // the kill lands, and `--max-old-space-size` in package.json bounds V8 while + // Turbopack allocates from Rust outside it (measured: lowering it to 2048 + // moved the peak by 23 MB, i.e. noise). + // + // Measured effect, local cold build of all 403 pages, peak single-process RSS: + // 5191 MB -> 4757 MB (-434 MB, -8.4%); compile 22.6s -> 19.5s. + // + // Set explicitly on purpose. Next documents this flag's build-time default as + // following `productionBrowserSourceMaps` (false), but the server-side maps are + // emitted anyway -- naming it is what suppresses them. + // + // Deliberately NOT paired with `turbopackMinify: false`, which takes a further + // 972 MB off the peak: it inflates client JS from 5.8 MB to 16 MB (+176%), a + // cost real readers pay on every visit. Confining minification to the server + // side -- where the memory actually goes, 589 MB of server chunks against + // 5.8 MB of client -- is not on offer: `experimental.serverMinification` is + // read only by `dist/build/webpack-config.js` and never on the Turbopack path. + turbopackSourceMaps: false, }, typescript: { ignoreBuildErrors: false,