close
Skip to content

feat(build): summarise the app (client) build at the end of nuxt build - #1663

Draft
atinux wants to merge 1 commit into
feat/quiet-server-buildfrom
feat/client-bundle-summary
Draft

atinux wants to merge 1 commit into
feat/quiet-server-buildfrom
feat/client-bundle-summary

Conversation

@atinux

@atinux atinux commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Written by an AI agent on behalf of @atinux. @atinux has not reviewed it yet.

Stacked on #1662 (quieter server build output). Review that one first; this diff only adds the client summary on top of it.

After nuxt build, Vite lists every client file with a hashed name (_nuxt/IS_fkJ2V.js 117.31 kB). That tells you nothing about why the bundle is that size: which packages are in it, what every page has to download, or what to change to make it smaller.

What changed

  • nuxt build (and nuxt generate) now end with a app (client) summary:
    • the total size, raw and gzip, and what every page loads (the entry, its static imports and their CSS)
    • size by package, with bars; your own code and Nuxt/Vite virtual modules get one row each
    • the largest chunks, each with its role (entry, every page, lazy app/pages/ws.vue, shared by N chunks) and its main contents
    • hints: a package bundled more than once (each version and what imports it), and a package other than Vue, Nuxt, vue-router or unhead that adds 50 kB or more to every page (with the file that imports it)
    • a pointer to npx nuxt-bundle-analyzer for the full picture
  • Vite's per-file client listing is hidden, and so is Vite's own gzip pass on the client (reportCompressedSize: false), since the summary computes gzip itself.
  • Projects need no setup. The CLI registers a small Vite plugin on vite:extendConfig that reads the Rolldown bundle in generateBundle, along with getModuleInfo importers. It does nothing with --logLevel silent or with webpack/rspack.

Evidence

The playground, nuxt build, exit code 0, client section only (the Nitro part comes from #1662).

Before (on main; #1662 doesn't touch the client output):

● Bundling client
ℹ Building client...
ℹ vite v8.3.5 building client environment for production...
transforming...
✓ 170 modules transformed.
rendering chunks...
computing gzip size...
ℹ .nuxt/dist/client/manifest.json                   3.49 kB │ gzip:  0.52 kB
.nuxt/dist/client/_nuxt/ws.Owk9mDUs.css           0.08 kB │ gzip:  0.09 kB
.nuxt/dist/client/_nuxt/error-500.BXhsl4_b.css    1.95 kB │ gzip:  0.72 kB
.nuxt/dist/client/_nuxt/error-404.BHD56dvq.css    2.70 kB │ gzip:  0.92 kB
.nuxt/dist/client/_nuxt/D0_HFpjZ.js               0.37 kB │ gzip:  0.28 kB
.nuxt/dist/client/_nuxt/DnmIjkub.js               1.71 kB │ gzip:  0.93 kB
.nuxt/dist/client/_nuxt/BjwMuEZy.js               2.68 kB │ gzip:  1.18 kB
.nuxt/dist/client/_nuxt/7P8NNFxW.js               4.03 kB │ gzip:  1.72 kB
.nuxt/dist/client/_nuxt/BY4l5XNd.js              75.54 kB │ gzip: 29.28 kB
.nuxt/dist/client/_nuxt/IS_fkJ2V.js             117.31 kB │ gzip: 42.79 kB

ℹ ✓ built in 492ms
✔ Client built in 518ms

After:

● Building app (client)
ℹ Building client...
ℹ vite v8.3.5 building client environment for production...
transforming...
✓ 170 modules transformed.
rendering chunks...
ℹ ✓ built in 456ms
✔ Client built in 478ms
App (client)  206.18 kB (77.00 kB gzip)  6 JS, 3 CSS
Every page loads 192.60 kB (71.12 kB gzip)

By package              size       gzip
@vue/runtime-core   56.39 kB   21.64 kB  ████████████
nuxt                52.52 kB   19.38 kB  ███████████░
vue-router          23.61 kB    8.50 kB  █████░░░░░░░
@vue/reactivity     15.79 kB    6.06 kB  ███░░░░░░░░░
unhead              14.37 kB    5.17 kB  ███░░░░░░░░░
@vue/runtime-dom    10.45 kB    3.76 kB  ██░░░░░░░░░░
ofetch               4.22 kB    1.52 kB  █░░░░░░░░░░░
devalue              3.94 kB    1.42 kB  █░░░░░░░░░░░
+10 more, 20.11 kB

Largest chunks          size       gzip
_nuxt/ChRpZFAW.js  117.06 kB   42.13 kB  entry nuxt, vue-router, unhead +11
_nuxt/BY4l5XNd.js   75.54 kB   28.99 kB  every page @vue/runtime-core, @vue/rea…
_nuxt/7P8NNFxW.js    4.03 kB    1.72 kB  lazy error-404.vue (nuxt)
_nuxt/BjwMuEZy.js    2.68 kB    1.18 kB  lazy error-500.vue (nuxt)
_nuxt/DnmIjkub.js    1.72 kB      929 B  lazy app/pages/ws.vue
_nuxt/D0_HFpjZ.js      379 B      280 B  lazy app/pages/index.vue

See what each chunk imports with npx nuxt-bundle-analyzer

A throwaway app with yaml imported in app.vue, acorn in a <LazyEditor> component, and two semver versions (7.8.5 directly, 6.3.1 through a local legacy package), coloured, full build output:

nuxt build output with the app (client) summary

Also checked:

  • playground-nightly (Nuxt 5 nightly, where Nitro runs the Vite environment build): the summary prints and the client file list is hidden.
  • pnpm test:types, eslint and knip pass.
  • The new client-bundle.spec.ts tests pass: the initial-load set, package attribution including nested copies, duplicate importers, the hint rules, and row truncation.
  • The whole unit suite passes (env -u NO_COLOR TERM=xterm-256color pnpm exec vitest run packages: 154 files, 2183 tests passed).

Details

  • Sizes per package are approximate. Rolldown reports each module's length before minification, so each module gets its share of the chunk's final (minified) size. That's enough to rank packages; nuxt-bundle-analyzer and Sonda use source maps for exact numbers.
  • Hiding Vite's list: the CLI wraps Nuxt's Vite customLogger and drops the messages that name a client file collected in generateBundle. Matching on file names rather than an output directory works whether buildDir is .nuxt or node_modules/.cache/nuxt/.nuxt, and with Nitro writing straight to .output/public. Server output goes through the same logger and is left alone.
  • Open questions:
    • Should @vue/* show as one vue row?
    • Should the summary be opt-out (a flag or config)?
    • Are the hint thresholds right?
    • Should the summary say anything for webpack/rspack builds?

ELI5

When you build a Nuxt app, the CLI now tells you how much JavaScript your visitors download, which libraries that weight comes from, and which ones you could load later or deduplicate to make pages lighter.

@atinux
atinux force-pushed the feat/client-bundle-summary branch from 18602f5 to 38d07fe Compare November 8, 2026 08:33
@atinux atinux changed the title feat(build): summarise the client bundle at the end of nuxt build feat(build): summarise the client app at the end of nuxt build Nov 8, 2026
@atinux
atinux force-pushed the feat/client-bundle-summary branch from 38d07fe to 5ab9fa8 Compare November 8, 2026 08:45
@atinux atinux changed the title feat(build): summarise the client app at the end of nuxt build feat(build): summarise the app (client) build at the end of nuxt build Nov 8, 2026
@atinux
atinux force-pushed the feat/client-bundle-summary branch 2 times, most recently from aba7cd5 to 71dc0ba Compare November 8, 2026 08:57
@atinux
atinux force-pushed the feat/client-bundle-summary branch from 71dc0ba to 2c6edf6 Compare November 8, 2026 09:14
@atinux
atinux force-pushed the feat/client-bundle-summary branch from 2c6edf6 to 893a812 Compare November 9, 2026 22:38

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant