Build Process for Open-SEO: Vite, TypeScript, and Cloudflare Workers Explained

Open-SEO uses a two-step build pipeline where Vite bundles the React UI and Cloudflare Worker code, followed by TypeScript type-checking via tsc --noEmit, with a custom lean-worker plugin that enforces bundle size constraints by stubbing heavy dependencies and validating against a deny-list.

The every-app/open-seo repository is a modern React application designed to run on Cloudflare's edge network. Understanding its build process is essential for contributors who need to optimize bundle sizes and ensure type safety before deployment.

Build Pipeline Overview

The complete build sequence spans five distinct phases, orchestrated through package.json scripts and driven by vite.config.ts, vite-plugin-lean-worker-bundle.ts, and tsconfig.json. The pipeline uses pnpm for dependency management and produces a lean Cloudflare Worker bundle optimized for edge execution.

  • Dependency installation via pnpm install
  • Vite bundling with custom lean-worker plugin
  • TypeScript validation using tsc --noEmit
  • Optional source-map generation for PostHog error tracking
  • Cloudflare deployment via Wrangler CLI

Step-by-Step Build Configuration

1. Dependency Installation with pnpm

Before building, install dependencies using the project's specified package manager. The package.json defines all scripts and dependency requirements for the build process.

pnpm install

2. Vite Build Process (vite.config.ts)

The core bundling logic resides in vite.config.ts, which configures Vite to handle both the React UI and the Cloudflare Worker entry points. The configuration loads environment variables via loadEnv and registers a specific plugin sequence:

// vite.config.ts (lines 45-60)
plugins: [
  leanWorkerBundle(),
  showDevtools ? devtools({ /* … */ }) : null,
  cloudflare({ inspectorPort: false, viteEnvironment: { name: "ssr" } }),
  tsConfigPaths(),
  tanstackStart(),
  viteReact(),
  tailwindcss(),
],

The build script in package.json (lines 13-14) invokes Vite with "build": "vite build && tsc --noEmit". This command handles React component compilation, Tailwind CSS processing, and Worker-specific bundling through the @cloudflare/vite-plugin.

3. TypeScript Validation (tsconfig.json)

After Vite completes bundling, the pipeline runs TypeScript checking separately to ensure type safety without emitting additional files. The tsconfig.json specifies "noEmit": true (lines 21-22), guaranteeing that only type validation occurs while Vite handles all file generation.

// tsconfig.json
"noEmit": true

This separation allows Vite to handle fast transformation and bundling while TypeScript enforces strict type checking as a final validation gate.

4. Source Map Generation (Optional)

The build conditionally generates source maps based on the POSTHOG_SOURCEMAPS environment variable. When set to true, Vite outputs to dist-sourcemaps instead of the standard dist directory.

// vite.config.ts (lines 42-44)
build: {
  sourcemap: emitSourcemaps,
  outDir: emitSourcemaps ? "dist-sourcemaps" : "dist",
}

Enable source maps for production error tracking with:

POSTHOG_SOURCEMAPS=true npm run build

5. Cloudflare Worker Deployment

The final step uploads the bundle to Cloudflare's edge network. The package.json provides two deployment paths:

  • npm run deploy: Runs database migrations, builds the project, and executes wrangler deploy
  • npm run deploy:postgres: Uses the Alchemy CLI with a production .env file for staged deployments

The Lean Worker Bundle Plugin

A critical component of the build process is the custom leanWorkerBundle() plugin defined in vite-plugin-lean-worker-bundle.ts. This plugin ensures the Cloudflare Worker bundle remains small and fast by implementing three specific optimizations:

Dependency Aliasing: Heavy packages like just-bash and workers-ai-provider are replaced with stub files to prevent them from entering the eager bundle.

Zod Locale Swapping: The plugin replaces Zod's multilingual locale barrel with an English-only export to reduce bundle size:

// Replaces with: export { default as en } from "./en.js"

Eager-Denylist Enforcement: During the generateBundle phase (lines 76-84), the plugin traverses the static import graph of worker entry chunks. If any module matches the deny-list (including dataforseo-client, autumn-js, or cheerio), the build immediately fails with an explicit error message.

The core validation logic appears at lines 65-66 (description) and 76-84 (deny-list generation) in vite-plugin-lean-worker-bundle.ts.

Build Commands Reference

Execute these commands from the repository root to manage the build lifecycle:


# Install dependencies (run once)

pnpm install

# Standard production build

npm run build

# Build with source maps for PostHog error tracking

POSTHOG_SOURCEMAPS=true npm run build

# Preview the production build locally

npm run preview

# Deploy to Cloudflare (after building)

npm run deploy

Key Configuration Files

File Purpose Location
package.json Build scripts and dependency definitions package.json
vite.config.ts Vite configuration, environment handling, plugin registration vite.config.ts
vite-plugin-lean-worker-bundle.ts Custom plugin enforcing bundle size constraints vite-plugin-lean-worker-bundle.ts
tsconfig.json TypeScript compiler options with strict mode tsconfig.json
docs/LOCAL_DEVELOPMENT.md Local development and build instructions docs/LOCAL_DEVELOPMENT.md

Summary

  • Open-SEO uses Vite for bundling and TypeScript (tsc --noEmit) for type validation in a sequential build pipeline defined in package.json.
  • The custom lean-worker bundle plugin stubs heavy dependencies, minimizes Zod locales to English-only, and enforces a deny-list that fails the build if bloated modules enter the worker bundle.
  • Source maps are conditionally emitted to dist-sourcemaps when POSTHOG_SOURCEMAPS=true, otherwise output goes to dist.
  • Deployment targets Cloudflare Workers via Wrangler or the Alchemy CLI for production environments.
  • All configuration is centralized in vite.config.ts, tsconfig.json, and the custom plugin file at the repository root.

Frequently Asked Questions

What build tool does Open-SEO use?

Open-SEO uses Vite as its primary build tool and bundler. Vite handles the React UI compilation, Tailwind CSS processing, and Cloudflare Worker bundling through the @cloudflare/vite-plugin. The build process is orchestrated via npm scripts defined in package.json that execute vite build followed by TypeScript type-checking.

How does Open-SEO keep the Cloudflare Worker bundle size small?

The repository implements a custom lean-worker bundle plugin (vite-plugin-lean-worker-bundle.ts) that stubs heavy dependencies (like just-bash and workers-ai-provider), replaces Zod's multilingual locales with an English-only barrel, and maintains an eager-denylist that aborts the build if prohibited modules (such as cheerio or dataforseo-client) are detected in the static import graph.

What is the purpose of the TypeScript --noEmit flag in the build?

The --noEmit flag ensures TypeScript performs type-checking without generating output files. Since Vite handles all compilation and bundling, this flag prevents file system conflicts and keeps the build fast while maintaining strict type safety. The tsconfig.json explicitly sets "noEmit": true to enforce this behavior.

How do I generate source maps for error tracking in Open-SEO?

Set the environment variable POSTHOG_SOURCEMAPS to true before running the build command: POSTHOG_SOURCEMAPS=true npm run build. This instructs Vite to emit source maps into the dist-sourcemaps directory rather than the standard dist folder, enabling detailed error tracking in PostHog or similar observability platforms.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →