Utix enforces per-feature-slice bundle size budgets to prevent any single tool from quietly pulling in heavy dependencies that degrade load performance.
With an architecture built around independent, lazy-loaded feature slices, a generic total bundle size budget does not work. Each tool loads only its own chunk, so we need per-slice budgets that:
- Prevent one heavy tool from hiding behind another's budget
- Correctly attribute shared/core code versus slice-specific code
- Allow independent contributors to add features without global coordination
- Catch dependency bloat at PR time, not after user complaints
150 KB per feature slice (uncompressed JavaScript)
This covers typical tool implementations including form validation, API calls, formatting, and result display.
220 KB for the shared route shell (/tools/[slug])
Per-feature slice budgets do not expose shared-chunk regression or the cost paid before a user reaches a tool. Utix measures route-level initial-load overhead from the build manifest:
- Shared entrypoint chunks (
/_app, webpack, root layout files) - Dynamic route container (
/tools/[slug]/page.tsx) - Baseline tracking with stable tolerance (
$\pm 5%$ ) to prevent false positives from build hashes.
Tools that perform deep cryptographic decoding or compile Soroban contract ASTs require heavier dependencies (such as @stellar/stellar-sdk and @stellar/stellar-xdr). These tools have an explicit, documented exception path governed by scripts/bundle-budget-exceptions.json:
| Feature | Exception Budget | Justification |
|---|---|---|
xdr-inspector |
350 KB | Deep recursive XDR parser and Soroban contract type schemas |
soroban-decoder |
300 KB | Full Soroban ScVal decoding and contract auth types |
transaction-decoder |
250 KB | Transaction envelope and operation XDR schemas |
soroban-spec-viewer |
280 KB | Contract function specifications and environment interface bindings |
- Ensure the heavy dependency cannot be split or loaded on-demand.
- Add an entry in
scripts/bundle-budget-exceptions.jsonwithbudgetKB,justification, andapproved: true. - If the tool stays within its exception ceiling,
scripts/bundle-budget.mjsmarks it asEXCEPTION_APPROVED. If it exceeds the ceiling, it fails withEXCEEDED.
Some tools have adjusted budgets based on their requirements:
| Feature | Budget | Justification |
|---|---|---|
| payment-qr | 180 KB | QR code generation library |
| address-validator | 120 KB | Pure validation logic, no heavy deps |
| balance-viewer | 150 KB | Default budget |
| trustline-checker | 100 KB | Simple lookup operation |
| transaction-lookup | 140 KB | Moderate API interaction |
| freighter-connect | 80 KB | Minimal wallet detection |
| testnet-faucet | 90 KB | Single API call |
npm run build
node scripts/bundle-budget.mjsThe check runs automatically as part of the build process. Budget violations fail the CI pipeline.
For integration with tooling:
node scripts/bundle-budget.mjs --jsonExample output:
Feature Size Budget Status
--------------------------------------------------------------------------------
payment-qr 165 KB 180 KB OK
address-validator 118 KB 120 KB OK
balance-viewer 155 KB 150 KB EXCEEDED
Status values:
OK- Within budgetEXCEEDED- Over budget, PR will fail
The bundle-baseline.json file tracks historical sizes to detect regressions:
{
"address-validator": {
"totalSize": 102400,
"sizeKB": 100,
"timestamp": "2026-09-25T00:00:00.000Z"
}
}After intentional changes that affect bundle size:
npm run build
node scripts/bundle-budget.mjs --update-baselineCommit the updated baseline with your PR.
- Feature slice implementation code
- Feature-specific dependencies
- Lazy-loaded chunks for the feature route
- Shared core modules (
core/*) - Next.js runtime and framework code
- Vendor chunks shared across all features
- Static assets (CSS, images)
This ensures each feature is only charged for its incremental cost.
If your feature exceeds its budget:
-
Review dependencies - Did you add a heavy library?
- Check package size on npm or bundlephobia.com
- Consider lighter alternatives
- Use dynamic imports for optional functionality
-
Code-split heavy operations
- Move heavy computations to web workers
- Lazy load rarely-used components
- Split large utility functions
-
Optimize imports
- Import only what you need:
import { specific } from 'lib' - Avoid entire libraries:
import * as lib from 'lib' - Check for duplicate dependencies in package-lock.json
- Import only what you need:
-
Request budget increase
- If the feature genuinely requires more budget
- Document why in the PR description
- Update
FEATURE_BUDGETSinscripts/bundle-budget.mjs - Update this documentation
New features inherit the default budget (150 KB) unless specified otherwise.
To set a custom budget:
- Edit
scripts/bundle-budget.mjs - Add entry to
FEATURE_BUDGETSobject:const FEATURE_BUDGETS = { 'your-feature': 120, // KB };
- Document the justification in your PR
The bundle budget check runs after build:
- name: Check bundle budgets
run: |
npm run build
node scripts/bundle-budget.mjsExit codes:
- 0 - All features within budget
- 1 - Budget violations detected
- 2 - Script error (missing build output, etc.)
To verify the budget system catches violations:
- Add a deliberately heavy dependency to a test feature
- Run
npm run build && node scripts/bundle-budget.mjs - Confirm the check fails with
EXCEEDEDstatus - Remove the dependency
As the project grows:
- Monitor baseline drift over time
- Review whether default budget remains appropriate
- Consider stricter budgets for simple tools
- Document rationale for budget increases
- Keep budgets tight enough to prevent bloat, loose enough to allow reasonable implementations
- Check bundle size before requesting review
- Update baseline only when intentional
- Never increase budget to bypass review
- Document heavy dependencies in PR description
- Consider performance impact on slow connections
- Remember: users on mobile networks pay for every KB