audit-bundles
Skill106 starsChanged 7 months agoMITView on GitHub
Analyze a pracht production build for client bytes per route, heavy vendor chunks, and large dependencies, and recommend code-splitting and prefetch changes. Use for "audit bundles", "why is my JS so big", "bundle size per route", "what's in my vendor chunk".
In this repository33 other files it gives its agents.
- AGENTS.mdAGENTS.md
- add-authSkill
- add-capabilitiesSkill
- add-contentSkill
- add-dbSkill
- add-i18nSkill
- add-imagesSkill
- add-observabilitySkill
- add-openapiSkill
- audit-a11ySkill
- audit-agent-surfaceSkill
- audit-authSkill
- audit-csrfSkill
- audit-depsSkill
- audit-headersSkill
- audit-islandsSkill
- audit-loadersSkill
- audit-redirectsSkill
- audit-secretsSkill
- audit-seoSkill
- audit-shellsSkill
- configure-isgSkill
- migrate-nextjsSkill
- pracht-debugSkill
- pracht-deploySkill
- pracht-scaffoldSkill
- pracht-test-apiSkill
- pre-deploySkill
- scaffold-e2eSkill
- scaffold-testsSkill
- tune-render-modeSkill
- typed-routesSkill
- upgrade-prachtSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 3 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 41
Do not treat exit 1 as "no output" — parse stdout anyway, or run with --no-budget-fail to keep the exit code clean.
- Very specific: line 84
Only "full" routes pay route JS + shared entry.
- Very specific: line 90
If the app declares budgets in the pracht plugin config, the Step 1 JSON also contains a budgets section with per-route pass/fail — lead …
- Very specific: line 146
If every route ends up on "none", do not stop there — the prefetch listeners still ship, in a chunk the router lazily imports on every page.
- Very specific: line 183
Verify any grouping change against the prerendered HTML, not just the numbers: a group broad enough to reshuffle entry chunks can drop the …
- Distinctive: line 202
When a route's CSS is small and the site is entered cold — a static export, a content site arriving from search — the stylesheet link is …
- Very specific: line 226
Always run pracht build --analyze --json first.
- Distinctive: line 228
Use the Vite manifest as the source of truth — chunk names rotate per build.
- Distinctive: line 233
Do not auto-edit. Bundle changes have render-blocking implications.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Pracht Audit Bundles
pracht performs route-level code splitting via the Vite plugin and emits a manifest. This skill reads that manifest, sizes each route's client payload, and surfaces the worst offenders.
Step 1: Build with analysis
MCP: if pracht dev-mcp is registered, prefer its inspect_*, doctor, and verify tools to the CLI.
pracht inspect build needs a prior pracht build.
pracht build --analyze --jsonThis is the native per-route payload report: for every route (pattern + render
mode) it emits the transitive client JS chunks with raw and gzip sizes
(routes[].chunks), route-specific and total sums (routeGzipBytes,
totalGzipBytes), and the shared entry chunks broken out (shared), sorted by
total gzip descending. A stale dist/ produces misleading numbers — rebuild,
don't reuse.
Exit-code footgun: when configured budgets fail, pracht build --analyze
--json sets a nonzero exit code while still printing valid JSON on stdout.
Do not treat exit 1 as "no output" — parse stdout anyway, or run with
--no-budget-fail to keep the exit code clean.
For a human-readable table, use pracht build --analyze instead.
Step 2: Pull supporting metadata
pracht inspect build --jsonCaptures the resolved adapter, client entry URL, and CSS/JS manifest. Cross
reference with dist/server/.vite/client-manifest.json for chunk metadata
(file, imports, dynamicImports, css, isEntry) — needed for vendor fan-in
analysis (Step 4) and CSS sizing, which the analyze report does not cover.
Size CSS from the cssManifest this command reports, never from
dist/server/.vite/client-manifest.json. A hydration: "none" or "islands" route is
absent from the client manifest by construction, so reading it directly reports
no CSS for a page whose document links a stylesheet — under-reporting exactly
the routes pracht recommends. inspect merges dist/server/css-manifest.json,
the build's own record, over the client manifest; an older build without that
file is client-only, and the CSS column should say so rather than read as zero.
Step 3: Interpret per-route payload
From the Step 1 JSON, report:
| Route | Hydration | Route JS (gz) | Shared (gz) | Total (gz) | CSS (gz) | LCP-class? |
gz = gzip size, taken directly from routeGzipBytes / shared.gzipBytes /
totalGzipBytes. Size CSS chunks (from cssManifest) with zlib.gzipSync.
Account for the per-route hydration field the analyze JSON emits (omitted
means "full") before judging sizes:
hydration: "none"routes ship 0 bytes of JS — the report already zeroes them out. Never flag them.hydration: "islands"routes never load the shared client entry; their total is the islands bootstrap plus every island chunk in the app — an upper bound, since which islands a page actually uses is only known at render time. Treat their totals as pessimistic.- Only
"full"routes pay route JS + shared entry.
LCP-class? is yes when total gz exceeds 200 KB — that's the order of
magnitude where mid-tier mobile starts losing LCP budget. Apply the threshold
after the hydration adjustment above.
If the app declares budgets in the pracht plugin config, the Step 1 JSON also
contains a budgets section with per-route pass/fail — lead the report with any
failures. If no budgets are configured, suggest adding them (see
https://pracht.resynapse.dev/docs/reference/config), starting from the current sizes plus ~10% headroom.
Step 4: Vendor chunk health
Identify chunks under node_modules/. For each:
- Size (raw and gz).
- Number of route chunks that import it (fan-in).
A vendor chunk imported by every route is the framework runtime — expected. A vendor chunk imported by ONE route is a code-splitting opportunity (move the import inside that route's component or lazy-load it).
A vendor chunk over 100 KB gz that is imported by every route is worth a manual review — common offenders: date libraries, validation libraries, icon sets, charting libraries.
Step 5: Heavy dependencies in route components
For each route chunk over 50 KB gz, run pracht inspect build --json plus
du-style analysis on the chunk contents:
- Grep the chunk source for known heavy module headers (
moment,lodash,chart.js,three,@stripe/stripe-js, etc.). - For each, recommend: (a) tree-shakeable alternative, (b) dynamic import
inside an event handler, (c) lazy-load via
lazy()from@pracht/core.
Step 6: Prefetch strategy
pracht inspect routes --json exposes prefetch per route on a current
@pracht/cli. If your CLI version predates the field (it's absent from the
JSON), fall back to grepping the route manifest source for prefetch:.
Route-level strategies are "none", "hover", "intent", "viewport"
("render" exists only as a per-link override — see below). Recommend:
"viewport"for primary-nav links."hover"for content links inside long pages."intent"(default) is fine if you're not sure."none"for routes that are large and rarely visited (admin, settings) so hover doesn't preload them.
Cost model for "viewport": the IntersectionObserver fires once per anchor —
it unobserves the anchor after the first prefetch, and prefetched route state
is cached for 30 seconds — so a link in a marketing footer costs at most one
prefetch per anchor per page view, not one per scroll. Viewport and render
prefetches run three at a time, drop links that left the page, and are skipped
on Save-Data or 2G connections. It's still wasteful
for a 300 KB route that footer visitors rarely click; prefer "none" or
"hover" there. Also note the per-link escape hatch: <Link prefetch="...">
overrides the route-level strategy for a single anchor (and accepts the extra
"render" value — prefetch as soon as the link renders, the most eager
option), so a route can stay on "intent" while its primary-nav link opts
into "viewport" or "render".
If every route ends up on "none", do not stop there — the prefetch listeners
still ship, in a chunk the router lazily imports on every page. Recommend
pracht({ client: { prefetch: false } }) instead, which compiles the whole
mechanism out (~2.6 KB gzip and one fewer request). Confirm nothing relies on
prefetching first: the router silently stops honouring route({ prefetch }) and
<Link prefetch>, and the imperative prefetch() export becomes a no-op. See
https://pracht.resynapse.dev/docs/prefetching#shipping-less-javascript.
Step 6b: Chunk-group composition
App-level chunking and pracht's vendor chunk coexist — recommend it freely.
pracht reads build.rollupOptions.output and contributes its Preact group in
whichever form the app used, so adding a group (to merge the long tail of small
initial chunks, say) does not cost the framework chunk:
// vite.config.ts
build: {
rollupOptions: {
output: {
codeSplitting: { groups: [{ name: "app", tags: ["$initial"], minSize: 20_000 }] },
},
},
}Two things to check when an app already configures chunking:
- Mixed forms. Rolldown lets
codeSplittingsilence bothadvancedChunksandmanualChunks. An app that sets more than one is running onlycodeSplitting; report the dead config aswarn. - A group that swallows Preact. Precedence is higher
priorityfirst, then declaration order, with pracht's group last — so an app group matchingnode_modulesat equal priority absorbs the framework runtime. If a separate, long-cached framework chunk was the intent, recommend aprioritybump or atestthat excludes Preact.
Verify any grouping change against the prerendered HTML, not just the numbers:
a group broad enough to reshuffle entry chunks can drop the stylesheet links
pracht injects per route. Compare <link rel="stylesheet"> tags in
dist/client/**/index.html before and after.
pracht({ vendorChunk: false }) makes pracht contribute nothing, for an app
that wants Preact inside its own chunks or places frameworkChunkGroups()
itself.
pracht contributes one group to the server build too: each island becomes
its own chunk, so a route that does not fully hydrate can resolve its CSS from
that build without inheriting the server entry's merged stylesheet. An app that
configures SSR chunking composes with it the same way the client side does; a
group that pulls islands back into the entry shows up as extra <link
rel="stylesheet"> tags on static pages, and the build warns.
build.cssCodeSplit: false is rejected outright — there is no index.html to
link the single stylesheet it produces, so every page would ship without one.
When a route's CSS is small and the site is entered cold — a static export, a
content site arriving from search — the stylesheet link is often the only
render-blocking request left on a page that ships no JavaScript. Report
pracht({ inlineCss: true }) as a suggestion there, with the cost: the same
bytes in every document and no shared stylesheet cache. An app whose visitors
move between pages should keep the link.
Step 7: Report
Three sections:
- Top 10 routes by total client payload — sorted desc.
- Top 10 vendor chunks by size — with fan-in.
- Suggestions — ordered by impact (KB saved × routes affected).
Tag every finding with a primary severity — error (budget failure), warn
(LCP-class route, single-route vendor chunk), info (tuning opportunity) —
and keep the size numbers as supporting detail.
Include before/after estimates for each suggestion: "Lazy-load chart.js
inside Component: -180 KB gz off /dashboard/analytics."
Rules
- Always run
pracht build --analyze --jsonfirst. A stale build is a source of bad advice. - Use the Vite manifest as the source of truth — chunk names rotate per build.
- Report gzip size, not raw — the wire size is what users pay.
- Distinguish "shared" code (entry + framework) from "route-specific" code when reporting; users can only optimize the latter.
- Do not auto-edit. Bundle changes have render-blocking implications; surface and let the user choose.
$ARGUMENTS
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

