lunx.docs
DocsTutorialsProduction Builds

Production Builds

Understand how Lunx compiles, optimizes, and secures your application for production deployment. Learn about SWC compilation, LightningCSS, code splitting, and the security pipeline.

Build pipeline overview

When you run lunx build, the Rust compiler orchestrates these stages in order:

  1. Security scan — secrets, CVEs, SBOM, SRI (aborts on violations)
  2. Module resolution — resolves all imports, applies path aliases and externals
  3. SWC compilation — TypeScript → JavaScript, JSX transform, decorator support
  4. LightningCSS — CSS transforms, nesting, vendor prefixes, minification
  5. Rollup bundling — tree-shaking, code splitting, chunk hashing
  6. Asset processing — images, fonts, SVGs inlined or hashed and copied
  7. HTML injection — SRI hashes, preload links, and manifest injected into index.html
bash
npm run build

 lunx v1.0.0  adapter: react  mode: production

🔍 Security pipeline...
  [1/4] Secret scan:    0 secrets in 84 files
  [2/4] CVE check:      0 critical, 0 high in 312 packages
  [3/4] SBOM:           dist/bom.json (CycloneDX 1.5)
  [4/4] SRI hashes:     injected into index.html

📦 Bundling...
  dist/assets/main-4f2c1a.js        92.4 kB  gzip: 29.8 kB
  dist/assets/vendor-9a3d2b.js      48.2 kB  gzip: 16.1 kB
  dist/assets/style-8d3f20.css      12.4 kB  gzip:  3.6 kB
  dist/assets/logo-3b1e9c.svg        2.1 kB
  dist/index.html                    1.1 kB
  dist/bom.json                      8.4 kB

 built in 533ms

Code splitting

Lunx performs automatic code splitting. Every dynamic import() creates a separate chunk that is loaded on demand. Shared vendor dependencies are split into a stable chunk that is cached across deployments.

typescript
// Automatic code splitting with React.lazy
import { lazy, Suspense } from 'react'

const AdminPanel = lazy(() => import('@/pages/AdminPanel'))
const Analytics  = lazy(() => import('@/pages/Analytics'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/admin"     element={<AdminPanel />} />
        <Route path="/analytics" element={<Analytics />} />
      </Routes>
    </Suspense>
  )
}

Manual chunks

Use build.rollupOptions.output.manualChunks to control which modules land in which chunk. This is useful for ensuring large libraries are cached separately from application code.

typescriptlunx.config.ts
export default defineConfig({
  framework: 'react',
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules/react'))      return 'react-vendor'
          if (id.includes('node_modules/@radix-ui'))  return 'ui-vendor'
          if (id.includes('node_modules/chart.js'))   return 'charts'
        }
      }
    }
  }
})

Asset handling

Files imported in your source code are processed automatically:

  • Files under 4KB are inlined as base64 data URIs (configurable via build.assetsInlineLimit)
  • Files over 4KB are copied to dist/assets/ with a content hash in the filename
  • SVG files can be imported as React components via the @lunx/plugin-svg plugin
typescript
// Static URL import (content-hashed)
import logoUrl from '@/assets/logo.png'        // '/assets/logo-3b1e9c.png'

// SVG as React component (with @lunx/plugin-svg)
import { ReactComponent as Logo } from '@/assets/logo.svg'

// JSON files are imported and tree-shaken
import config from '@/config/defaults.json'

Environment variables in builds

bash.env.production
# Only LUNX_PUBLIC_ variables are bundled into client code
LUNX_PUBLIC_API_URL=https://api.example.com
LUNX_PUBLIC_SENTRY_DSN=https://...@sentry.io/123

# These remain server-side only (never in bundle)
DATABASE_URL=postgres://...
STRIPE_SECRET_KEY=sk_live_...

Analyzing bundle size

bash
# Generate an interactive bundle size report
lunx build --analyze

# Opens a treemap visualization at http://localhost:8888
# showing every module and its contribution to bundle size
Keep vendor chunks stable
Use manualChunks to separate vendor libraries from your app code. Vendor chunks rarely change, so users benefit from long-term browser caching across deploys. Only the app chunk gets a new hash when you ship a feature.