|$ curl https://forge-ai.dev/api/markdown?path=docs/build-tools/best-practices
$cat docs/build-tools-best-practices.md
updated Todayยท28-36 min readยทpublished

Build Tools Best Practices

โ—†Build Toolsโ—†Best Practicesโ—†Intermediate๐ŸŽฏFree Tools
Introduction

Good build tooling is invisible when it works and catastrophic when it lies โ€” wrong env values in client bundles, missing source maps, or CI that rebuilds the world. These practices apply across Vite, Webpack, Rspack, Parcel, tsup, and Bun.

โ„น

info

Optimize for correctness first (deterministic outputs, safe env boundaries), then speed (caches, faster compilers), then DX flourishes.
Choose Tools by Requirements

Write requirements before picking brands. See the full matrix in Choosing a Bundler.

RequirementPracticeAnti-pattern
App with HMRVite / framework bundlerUsing tsup as an app bundler
Publish librarytsup/Rollup + dual exportsBundling React into the package
Webpack debt + speedRspack migrationBig-bang rewrite without gates
Monorepo CI timeTurborepo/Nx + remote cacheFaster laptop โ‰  fixed CI
Dev / Prod Parity

Dev servers optimize for speed (native ESM, less minify). Production optimizes for size and caching. Parity means behavior matches โ€” not that pipelines are identical.

AreaKeep alignedOK to differ
Env semanticsSame public env names & shapesLocal-only debug flags
Path aliasesIdentical resolve rulesโ€”
Feature flagsSame evaluation rulesDead-code elimination aggressiveness
CSS orderNo divergent cascade bugsCSS minify
PolyfillsSame browser targetsDev may skip heavy polyfills
โš 

warning

Regularly run a production build locally (vite build && vite preview / next build) before merging large dependency or alias changes. Dev-only success is not enough.
Source Maps

Source maps reconnect minified production stack traces to original TypeScript/JSX. Without them, production debugging is guesswork.

vite.config.ts (maps)
TypeScript
1import { defineConfig } from "vite";
2
3export default defineConfig({
4 build: {
5 // "hidden" emits .map files without a public //# sourceMappingURL
6 sourcemap: "hidden",
7 },
8});
ModeWhenSecurity note
true / inlineLocal debugDo not ship inline maps to public CDN casually
hiddenProd + private uploadPreferred for many SaaS apps
disabledRareOnly if you accept blind prod errors
โœ“

best practice

Upload maps to Sentry/Datadog/etc. in CI and retain them by release version. Delete or restrict public access to .map on the CDN.
Env Injection Safety

Bundlers inline compile-time constants. Anything prefixed for the client (VITE_*, NEXT_PUBLIC_*) becomes world-readable in the JS bundle.

unsafe-vs-safe.js
JavaScript
1// UNSAFE โ€” secret ends up in the client bundle
2const BAD = process.env.DATABASE_URL;
3fetch("/api", { headers: { Authorization: process.env.SECRET_API_KEY } });
4
5// SAFE โ€” only public config in client code
6const API_BASE = import.meta.env.VITE_API_BASE_URL;
7
8// SAFE โ€” secrets only on the server
9// server.ts
10const dbUrl = process.env.DATABASE_URL;
PracticeWhyHow
Allowlist public envPrevent accidental leakageFramework prefixes + review
Validate env at bootFail fast on misconfigzod / t3-env / custom schema
Scan bundlesCatch leaked secretsCI grep for known secret patterns
List env in cache keysAvoid wrong turbo/nx hitsDeclare in turbo/nx config
โœ•

danger

Never use DefinePlugin / define to inject private keys into client entrypoints. Treat client bundles as public documents.
CI Caching

Cache at multiple layers. Missing one layer leaves free performance on the table.

LayerWhat to cacheKey by
Installpnpm/npm/yarn storelockfile hash
Taskturbo/nx outputsinputs + env + task def
Bundler FS cacheWebpack/Rspack cache dirsconfig + lockfile
Docker layersdeps before source copyDockerfile order
ci-cache-sketch.yml
YAML
1- uses: actions/cache@v4
2 with:
3 path: |
4 ~/.local/share/pnpm/store
5 .turbo
6 node_modules/.cache
7 key: ${{ runner.os }}-build-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}
Analyzing Bundles

Measure before optimizing. Bundle analyzers show duplicated packages, accidental polyfills, and oversized locales.

analyze.sh
Bash
1# Vite
2npx vite-bundle-visualizer
3
4# Webpack / Rspack โ€” webpack-bundle-analyzer or similar
5# Ensure stats JSON is generated in CI artifacts for regressions
6
7# Size budgets in CI (fail PR if main chunk exceeds threshold)
8# Use bundlesize, size-limit, or custom scripts on stats
  • Track compressed (gzip/brotli) sizes โ€” raw bytes mislead.
  • Set budgets on route-level chunks, not only total.
  • Investigate sudden jumps after dependency upgrades.
Avoiding Duplicate Dependencies

Two Reacts in one page break hooks. Two lodashes inflate size. Deduplication is both a package-manager and bundler concern.

TechniqueWhereWhen
peerDependenciesLibrariesAlways for React/Vue/etc.
pnpm overrides / resolutionsRoot package.jsonForce single version
Bundler alias / resolve.dedupeVite/WebpackWhen graph still duplicates
Module Federation sharedMF hosts/remotesSingleton runtime deps
vite.config.ts (dedupe)
TypeScript
1import { defineConfig } from "vite";
2
3export default defineConfig({
4 resolve: {
5 dedupe: ["react", "react-dom"],
6 },
7});
Tree Shaking & Side Effects
  • Prefer ESM packages with static import/export.
  • Mark "sideEffects": false honestly in libraries.
  • Avoid wildcard re-exports that defeat shaking when unnecessary.
  • Import locales/icons surgically (e.g. individual icon paths).
๐Ÿ“

note

esbuild/SWC transpile speed does not replace understanding side effects. A fast build of a 4MB bundle is still a 4MB bundle.
Deterministic Builds
  • Pin tool versions in lockfiles.
  • Avoid embedding wall-clock timestamps in banners.
  • Use content hashes in filenames for long-term caching.
  • Fail CI on unexpected diff of critical artifacts when doing reproducible builds.
Production Checklist
checklist.txt
TEXT
1[ ] Requirements documented (HMR, SSR, federation, dual package, CI budget)
2[ ] Tool choice matches product shape (app vs lib vs monorepo)
3[ ] Production preview smoke-tested locally
4[ ] Source maps generated and uploaded privately
5[ ] Client env allowlisted; secrets server-only
6[ ] Install + task caches configured in CI
7[ ] Bundle analyzed; budgets enforced on critical routes
8[ ] React/framework deduped; peers externalized in libs
9[ ] Lockfile committed; tool versions pinned
$Blueprint โ€” Engineering DocumentationยทSection ID: BT-BP-01ยทRevision: 1.0

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.