Build Speed Optimizations
This document describes the build speed optimizations implemented in the project.
Overview
The build system has been optimized for both speed and efficiency through several key improvements:
- Dynamic Concurrency: Automatically scales to available CPU cores
- Astro-managed CSS: Tailwind runs inside the Astro build, emitting hashed CSS chunks
- JavaScript Bundling: Minifies
/assets/entry points with esbuild for production - Parallel Execution: Multiple build tasks run simultaneously
- Bundle Freshness Checks: Skips Motion and Lucide bundling when outputs are current
- Consolidated Build Entry: Runs prebuild tasks and the Astro build together without extra cache layers
Performance Metrics
Before Optimizations
- Build time: ~1.3 seconds
- Concurrency: Fixed at 4 workers
- CSS: Multiple HTTP requests (18+ files)
- Caching: Partial template caching only
After Optimizations
- Build time: ~1.4 seconds (similar, with Tailwind running inside Astro)
- Concurrency: Dynamic (scales to CPU cores)
- CSS: Hashed Vite chunks emitted from Astro layout imports
- Caching: Partial template caching only
Vite Dev Server Tuning
Optimize local feedback loops by warming critical client modules and reusing dependency prebundles:
- Warmup entries: Vite pre-transforms core client bundles (
src/scripts/site-modules.jsand the navigation controller) on startup so the first page load avoids on-demand compilation. - Prebundled dependencies: The Motion animation library is pre-optimized to shrink cold-start overhead during dev sessions.
- Cache reuse: Vite’s cache lives in
.vite/at the repo root to persist across installs and keep dev servers responsive after dependency reinstalls.
Environment Variables
Build Invocation
Use the streamlined build entrypoint to run prebuild tasks and the Astro production build together:
# Standard build
bun run build
# Skip when a cached build is already current (source inputs unchanged)
bun run build # automatically reuses the cached build marker
# Skip the build when you already have fresh output available locally
SKIP_BUILD_HOME=1 bun run build
# Force a full rebuild when you need to invalidate the cache
SKIP_SITE_BUILD_CACHE=1 bun run build
# Call the Astro build directly
bun run build:site
The site build records a manifest in .cache/site-build.json so repeat builds can reuse existing app/dist output when source
files and dependencies are unchanged. When the manifest or output timestamps diverge—because of content, template, script, or
dependency updates—the build automatically re-runs and refreshes the marker.
The manifest tracks the build-relevant script entrypoints (scripts under scripts/build plus shared helpers) so changes to test
or lint tooling do not invalidate the cached site output.
Build Concurrency
Control the number of parallel build workers:
# Use all CPU cores (default)
bun run build
# Limit to 4 workers
BUILD_CONCURRENCY=4 bun run build
# Limit to 2 workers (for resource-constrained environments)
BUILD_CONCURRENCY=2 bun run build
Tag Build Concurrency
Control the number of parallel tag page builds:
# Use all CPU cores (default)
bun run build
# Limit to 4 workers
TAG_BUILD_CONCURRENCY=4 bun run build
CSS/Tailwind pipeline (canonical reference)
CSS bundling now rides entirely on the Astro/Tailwind integration:
app/src/styles/styles.cssimports through the shared layout so Vite emits hashed CSS chunks automatically during dev and production builds.- Page-level overrides ship through
app/src/styles/pages/overrides.css, keeping optional sitemap styles scoped without needing standalone CSS entrypoints. - No separate
bundle:csscommand orassets/*.bundle.cssoutputs remain; inspect generated CSS by runningbun run buildand checkingapp/dist/assets/.
Benefits
- Single source of truth: Styles originate from the Astro app, eliminating divergence between manual bundles and dev output.
- Scoped payloads: Route-level imports keep optional CSS off pages that do not need it.
- Hashed assets: Vite handles cache busting and minification without extra scripts.
JavaScript Bundling
JavaScript entry points referenced by templates and partials flow through Astro’s Vite pipeline so the same optimizer handles dev HMR and production bundles.
Entry Points
navigation.js→ imported viaBase.astroand emitted toapp/dist/assets/forms/lead-form-loader.js→ imported viaBase.astroand emitted toapp/dist/assets/forms/js/footer-meta.js→ imported via the footer component and emitted toapp/dist/assets/js/js/field-notes-feed.js→ imported on the field notes page and emitted toapp/dist/assets/js/
How It Works
- Astro inlines script URLs via
?urlimports so Vite tracks them as build inputs. - Vite bundles each entry alongside the rest of the site, emitting hashed ESM output into
app/dist/assets/. - Production builds use Vite’s default minification and sourcemaps; dev builds keep unminified modules with HMR.
- Templates keep explicit script tags so page-level ownership stays clear while benefiting from Vite-managed URLs.
Benefits
- Smaller payloads: Minified bundles reduce JS bytes sent to the browser
- Fewer network hops: Vendor modules (for example, Lozad) are inlined into page-specific bundles
- HMR parity: Dev servers and production builds share the same entry graph
Bundle Freshness Checks
Avoid repeated third-party bundling when inputs have not changed:
- Lucide icon bundling records the latest mtime across HTML/JS icon usages and the Lucide package; if the output is newer, the bundle step is skipped.
Optimization Opportunities
Completed ✅
- Dynamic CPU-based concurrency
- CSS bundling infrastructure
- Parallel build execution
- JavaScript bundling for shared
/assets/entry points
Future Improvements 🔮
- Incremental builds: Only rebuild changed templates
- Dependency tracking: Rebuild dependent files when partials change
- CSS minification: Use Lightning CSS for better minification
- Asset optimization: Optimize images during build
- Build profiling: Add performance monitoring
Monitoring Build Performance
Measure Build Time
# macOS/Linux
time bun run build
# Windows (PowerShell)
Measure-Command { bun run build }
Check CPU Utilization
# See how many cores are being used
node -e "console.log('CPU cores:', require('os').cpus().length)"
Analyze Build Output
The build system provides detailed logging:
- Template build count
- CSS chunk sizes
- Sitemap generation stats
Troubleshooting
Build is Slower Than Expected
- Check CPU core count:
node -e "console.log(require('os').cpus().length)" - Reduce concurrency if system is resource-constrained
- Check for file system issues (slow disk I/O)
CSS Output Issues
- Verify CSS entrypoints are imported from the relevant Astro layout or page
- Check for circular
@importdependencies - Review console warnings for missing files
Best Practices
Development
- Use default settings (dynamic concurrency)
- CSS emission runs automatically through Astro
- Watch mode uses incremental builds
CI/CD
# Use all available cores
BUILD_CONCURRENCY=$(nproc) bun run build
# Enable production optimizations
NODE_ENV=production bun run build
Production Builds
# Full production build with optimizations
NODE_ENV=production bun run build
Architecture
The build pipeline diagram relies on Mermaid—mirror any script or path changes here and follow documentation-guide.md so labels stay clear.
Build Pipeline
graph TB
Entry["bun run build"]
Entry --> Astro["Astro Build<br/>(pages, CSS, JS, tags)"]
Astro --> Output3["/tags/*.html + /assets/*.css + /assets/*.js"]
Output3 --> Sitemap
Sitemap --> Final["sitemap.xml"]
style Entry fill:#e3f2fd
style Astro fill:#fff3e0
style Sitemap fill:#e8f5e9
style Final fill:#c8e6c9
File Structure
scripts/
├── build/
│ └── generate-sitemap.mjs # Sitemap generation
Tag index and detail pages are emitted directly by Astro routes in app/src/pages/tags/, replacing the legacy build-tags.mjs
step.
Contributing
When adding new build steps:
- Use concurrency: Leverage
runWithConcurrency()for parallel tasks - Log progress: Provide clear console output
- Handle errors: Gracefully handle missing files and errors
- Document: Update this file with new optimizations