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:

  1. Dynamic Concurrency: Automatically scales to available CPU cores
  2. Astro-managed CSS: Tailwind runs inside the Astro build, emitting hashed CSS chunks
  3. JavaScript Bundling: Minifies /assets/ entry points with esbuild for production
  4. Parallel Execution: Multiple build tasks run simultaneously
  5. Bundle Freshness Checks: Skips Motion and Lucide bundling when outputs are current
  6. 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.js and 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.css imports 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:css command or assets/*.bundle.css outputs remain; inspect generated CSS by running bun run build and checking app/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 via Base.astro and emitted to app/dist/assets/
  • forms/lead-form-loader.js → imported via Base.astro and emitted to app/dist/assets/forms/
  • js/footer-meta.js → imported via the footer component and emitted to app/dist/assets/js/
  • js/field-notes-feed.js → imported on the field notes page and emitted to app/dist/assets/js/

How It Works

  1. Astro inlines script URLs via ?url imports so Vite tracks them as build inputs.
  2. Vite bundles each entry alongside the rest of the site, emitting hashed ESM output into app/dist/assets/.
  3. Production builds use Vite’s default minification and sourcemaps; dev builds keep unminified modules with HMR.
  4. 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 ✅

  1. Dynamic CPU-based concurrency
  2. CSS bundling infrastructure
  3. Parallel build execution
  4. JavaScript bundling for shared /assets/ entry points

Future Improvements 🔮

  1. Incremental builds: Only rebuild changed templates
  2. Dependency tracking: Rebuild dependent files when partials change
  3. CSS minification: Use Lightning CSS for better minification
  4. Asset optimization: Optimize images during build
  5. 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

  1. Check CPU core count: node -e "console.log(require('os').cpus().length)"
  2. Reduce concurrency if system is resource-constrained
  3. Check for file system issues (slow disk I/O)

CSS Output Issues

  1. Verify CSS entrypoints are imported from the relevant Astro layout or page
  2. Check for circular @import dependencies
  3. 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:

  1. Use concurrency: Leverage runWithConcurrency() for parallel tasks
  2. Log progress: Provide clear console output
  3. Handle errors: Gracefully handle missing files and errors
  4. Document: Update this file with new optimizations

References