This metrics tool terrifies bad developers

Start free trial
SitePoint Premium
Stay Relevant and Grow Your Career in Tech
  • Premium Results
  • Publish articles on SitePoint
  • Daily curated jobs
  • Learning Paths
  • Discounts to dev tools
Start Free Trial

7 Day Free Trial. Cancel Anytime.

Vite 8 (verify the current stable release at https://github.com/vitejs/vite/releases) introduces an internal architecture change: it replaces esbuild with Oxc as the default transformer and resolver. This changes how every React project handles JSX transforms, TypeScript stripping, dependency resolution, and plugin interoperability. For teams running production React applications on Vite, this migration requires you to update configuration, audit plugins, and verify builds. This guide walks through the complete migration from Vite 7 to Vite 8, with concrete configuration examples, a structured checklist, and performance context.

Note: The configuration keys and version numbers in this guide are based on pre-release Vite 8 documentation. Always verify exact config key names and compatible plugin versions against the official Vite 8 changelog and release notes before applying changes to a production project.

Table of Contents

Why Vite Is Replacing esbuild with Oxc

The Role esbuild Played in Vite's Architecture

Since Vite's earliest versions, esbuild served two critical roles: transforming TypeScript and JSX during development, and pre-bundling dependencies for the dev server. Evan You selected esbuild because it was 10-100x faster than Babel and Terser per esbuild's own published benchmarks (see https://esbuild.github.io/), which was transformative at the time. esbuild handled TypeScript type stripping (without type checking), JSX compilation, and converting CommonJS dependencies into ESM-compatible modules during the pre-bundling phase. This architecture gave Vite its signature sub-second dev server startup, but esbuild was always an external tool with its own release cadence, API surface, and design priorities that did not always align with Vite's evolving needs.

What Oxc Brings to the Table

Oxc (short for Oxidation Compiler) is a Rust-based toolchain (see https://oxc.rs/) that provides a unified parser, transformer, resolver, and linter. Unlike esbuild, which was designed as a standalone bundler that Vite adapted, the Oxc team is building it for direct integration into the Vite ecosystem as a core design goal. Oxc's transformer handles JSX, TypeScript, and syntax lowering in a single pass rather than dispatching to multiple tools. Its resolver replaces esbuild's module resolution with an implementation that aligns with Rolldown, the Rust-based Rollup replacement that Vite is converging on as its future bundler. The tighter coupling between Oxc and Rolldown eliminates serialization boundaries between transformation and bundling steps, an architectural advantage over the esbuild-based pipeline. See the performance comparison table below for directional estimates of the resulting speedups.

The tighter coupling between Oxc and Rolldown eliminates serialization boundaries between transformation and bundling steps, an architectural advantage over the esbuild-based pipeline.

What This Means for React Developers Specifically

For React projects, the switch affects three areas directly. JSX transform handling now runs through Oxc's transformer rather than esbuild's, so you specify JSX runtime configuration (automatic vs. classic) through Oxc's options instead of esbuild's jsxFactory and jsxFragment fields. React Fast Refresh (HMR) continues to be managed by @vitejs/plugin-react, but the underlying transform that the plugin hooks into has changed, requiring a compatible plugin version. TypeScript stripping behavior also differs in edge cases: Oxc handles const enum inlining and decorator metadata differently from esbuild, which can surface build errors in projects that rely on these TypeScript features.

Prerequisites and Pre-Migration Assessment

Minimum Requirements

Vite 8 requires Node.js 18.0 or later, dropping support for Node.js 16 (confirm the exact requirement by running npm view vite@8 engines or checking the Vite 8 release notes). Projects should be running React 18 or React 19. Package managers npm 9+, pnpm 8+, and Yarn 3+ are expected to be compatible with Vite 8's dependency resolution — verify against the release notes for your package manager.

Before proceeding, confirm your Node.js version:

node --version
# Must be >= 18.0.0

The @vitejs/plugin-react package must be updated to its Vite 8-compatible version — verify the current compatible version at https://www.npmjs.com/package/@vitejs/plugin-react before installing. Earlier versions bind directly to esbuild's transform API and will not work with Vite 8.

Auditing Your Current Vite Configuration

Before upgrading, examine your vite.config.ts for any direct references to the esbuild configuration block. Look for custom jsxFactory, jsxFragment, target, define, drop, and legalComments options. Catalog any third-party plugins that hook into esbuild's transform pipeline (identifiable by plugins that reference esbuild in their source or documentation) for compatibility review.

Here is a typical Vite 7 configuration for a React + TypeScript project, annotated with lines that will require changes:

// vite.config.ts — Vite 7 (esbuild-based)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
import svgr from 'vite-plugin-svgr'

export default defineConfig({
  plugins: [
    react(),          // Will need version upgrade for Vite 8
    tsconfigPaths(),  // Check for Vite 8 compatibility
    svgr(),           // Check for Vite 8 compatibility
  ],
  // ⚠️ This entire block is esbuild-specific and must be migrated
  esbuild: {
    jsxFactory: 'React.createElement',   // ⚠️ Moves to oxc config
    jsxFragment: 'React.Fragment',       // ⚠️ Moves to oxc config
    target: 'es2020',                    // ⚠️ Moves to oxc or build.target
    drop: ['console', 'debugger'],       // ⚠️ Requires migration
    legalComments: 'none',               // ⚠️ Requires migration
  },
  build: {
    target: 'es2020',
    sourcemap: true,
    minify: 'esbuild',  // ⚠️ Review minifier setting
  },
})

Step-by-Step Migration from Vite 7 to Vite 8

Step 1: Upgrading Vite and Core Dependencies

Begin by upgrading Vite itself and all official plugins. The @vitejs/plugin-react package must be updated to its Vite 8-compatible release. If the project uses @vitejs/plugin-react-swc, the same version alignment applies.

# Using pnpm (example; adapt for your package manager)
pnpm add -D vite@^8.0.0 @vitejs/plugin-react@^5.0.0

# Using npm
npm install -D vite@^8.0.0 @vitejs/plugin-react@^5.0.0

# Using Yarn
yarn add -D vite@^8.0.0 @vitejs/plugin-react@^5.0.0

# If using the SWC variant instead:
pnpm add -D vite@^8.0.0 @vitejs/plugin-react-swc@^5.0.0

# Repair lockfile after version changes (local development only)
pnpm install

# In CI, always use --frozen-lockfile to prevent lockfile drift:
# pnpm install --frozen-lockfile

⚠️ Peer dependency conflicts: If npm install reports peer dependency conflicts, investigate and resolve them explicitly rather than suppressing them with --legacy-peer-deps. Use that flag only as a last resort and document the conflict in your ADR, as it silently bypasses npm's peer dependency validation and can result in mutually incompatible packages in node_modules.

After running these commands, verify the installed versions with npx vite --version to confirm Vite 8 is active.

Step 2: Updating vite.config.ts for Oxc

The core configuration change involves removing the esbuild block and replacing it with oxc options. Oxc's configuration surface covers JSX runtime settings, TypeScript behavior, and syntax targeting.

⚠️ Verify the exact config key name: The key name shown here (oxc:) is based on pre-release documentation and may differ in the stable release. Check the official Vite 8 changelog and the UserConfig TypeScript type definitions (node_modules/vite/dist/node/index.d.ts) to confirm the correct field name before applying. Run: grep '"oxc"' node_modules/vite/dist/node/index.d.ts — if there are zero matches, the key name has changed and this config block will be silently ignored.

// vite.config.ts — Vite 8 (Oxc-based)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
import svgr from 'vite-plugin-svgr'

export default defineConfig({
  plugins: [
    // Pass jsxRuntime to match oxc.jsx.runtime below.
    // Use 'automatic' for React 17+ projects.
    react({ jsxRuntime: 'classic' }),
    tsconfigPaths(),  // Check changelog for Vite 8 / Oxc resolver support
    svgr(),           // Verify Vite 8 support in plugin changelog
  ],
  // ⚠️ VERIFY: confirm 'oxc' is the correct UserConfig key in Vite 8 stable.
  // Check: grep '"oxc"' node_modules/vite/dist/node/index.d.ts
  oxc: {
    // JSX configuration — replaces esbuild.jsxFactory / jsxFragment
    jsx: {
      runtime: 'classic',                    // 'automatic' | 'classic'
      pragma: 'React.createElement',         // Used when runtime is 'classic'
      pragmaFrag: 'React.Fragment',          // Used when runtime is 'classic'
    },
    // TypeScript handling
    typescript: {
      // This path is relative to the Vite config file location.
      // Adjust for monorepo layouts.
      tsconfig: './tsconfig.json',
      decorators: true,                      // Enable experimental decorators — see note below on Stage 1 vs Stage 3
      onlyRemoveTypeImports: true,           // Aligns with isolatedModules behavior:
                                             // Vite sets isolatedModules implicitly because it
                                             // transforms files individually without full-program
                                             // type information.
    },
    // Target syntax version
    target: 'es2020',
  },
  build: {
    target: 'es2020',
    sourcemap: true,
    // Minifier setting: VERIFY the correct minifier key in Vite 8 release notes.
    // Use 'oxc' explicitly rather than `true`, because `true` may resolve to
    // 'esbuild' if the default has not changed. Confirm in the Vite 8 changelog.
    minify: 'oxc',
    // ⚠️ Console/debugger removal: if you previously used
    //   drop: ['console', 'debugger']
    // in the esbuild block, you must configure this under the minifier options
    // in Vite 8. Leaving this unconfigured will include console statements in
    // production output. Check the Vite 8 build options documentation for the
    // exact replacement syntax.
    //
    // Known-stable fallback using Terser (if Oxc drop options are not yet documented):
    //   minify: 'terser',
    //   terserOptions: {
    //     compress: { drop_console: true, drop_debugger: true },
    //   },
    //
    // This is a production safety concern — do not skip this step.
    // After building, verify: grep -rn 'console.log' dist/assets/*.js
    // Expected: no output (exit code 1 from grep = pass).
  },
})

For projects using the automatic JSX runtime (React 17+), the configuration simplifies:

  plugins: [
    react({ jsxRuntime: 'automatic' }),
    tsconfigPaths(),
    svgr(),
  ],
  oxc: {
    jsx: {
      runtime: 'automatic',
      importSource: 'react',       // Only active when runtime is 'automatic'
    },
    typescript: {
      tsconfig: './tsconfig.json',
    },
    target: 'es2020',
  },

Note on importSource: The importSource field only has effect when runtime is set to 'automatic'. It is not used in the classic runtime configuration.

Step 3: Adjusting Build and Preview Scripts

For most projects, the package.json scripts section requires no changes to command names, but any scripts passing esbuild-specific CLI flags need updating.

{
  "engines": {
    "node": ">=18.0.0"
  },
  "scripts": {
    "dev": "vite --force",
    "build": "tsc -b && vite build",
    "preview": "vite preview",
    "lint": "eslint .",
    "test": "vitest run",
    "build:analyze": "vite build --mode analyze"
  }
}

The vite build command in Vite 8 uses Oxc for transformation by default. No additional CLI flags are required to enable Oxc; it is the default pipeline. If a project previously passed --config with an esbuild-specific configuration file, that file must be updated to use the oxc block.

Note: The dev script uses vite --force to clear the pre-bundle cache after migration. Once you have confirmed the dev server works correctly with the new config, you can remove --force to restore normal caching behavior.

Step 4: Handling Plugin Compatibility

Plugins that hooked into esbuild's transform API directly (rather than through Vite's plugin API) will break in Vite 8. This affects plugins that used transformWithEsbuild or accessed esbuild internals through the config.esbuild object. The Vite plugin API's transform hook itself remains stable, so plugins using standard Vite hooks typically work without changes.

For the React ecosystem, check the following plugins for explicit Vite 8 support before upgrading:

  • @vitejs/plugin-react: required for Vite 8. Verify the compatible version at https://www.npmjs.com/package/@vitejs/plugin-react.
  • @vitejs/plugin-react-swc: if using SWC, verify the Vite 8-compatible version in the plugin's release notes. SWC handles its own transforms; verify coexistence behavior with Oxc in the release notes before relying on it.
  • vite-plugin-svgr and vite-tsconfig-paths: check each plugin's GitHub releases and peerDependencies for vite@^8 entries before upgrading.
  • vite-plugin-checker: runs type checking independently of the transformer, but compatibility with Vite 8 is unverified. Check the plugin's release notes for an explicit support statement.

For any plugin not listed, check the plugin's changelog or GitHub issues for Vite 8 support. Plugins that vendor their own esbuild binary will continue to function but represent redundant overhead.

Step 5: Verifying the Dev Server and HMR

After configuration changes, start the dev server with vite --force to clear any stale pre-bundle cache, and verify that React Fast Refresh works correctly. The following component exercises several TypeScript features that behave differently under Oxc:

// src/components/SmokeTest.tsx
import { useState, useEffect, useRef } from 'react'

// TypeScript enum — verify Oxc handles this correctly
enum Status {
  Idle = 'idle',
  Loading = 'loading',
  Error = 'error',
}

// Legacy Stage 1 decorator syntax — requires "experimentalDecorators": true
// in tsconfig.json. TypeScript 5.x uses Stage 3 decorators by default, which
// have a different signature. If your project uses Stage 3 decorators, update
// this example accordingly.
function withLogging(
  _target: object,
  key: string,
  descriptor: PropertyDescriptor,
): PropertyDescriptor {
  const original = descriptor.value as (...args: unknown[]) => unknown
  descriptor.value = function (...args: unknown[]) {
    console.log(`Calling ${key}`)
    return original.apply(this, args)
  }
  Object.defineProperty(descriptor.value, 'name', { value: key })
  return descriptor
}

class ApiService {
  @withLogging
  fetchData(signal: AbortSignal): Promise<Response> {
    return fetch(import.meta.env.VITE_API_URL ?? '/api/health', { signal })
  }
}

export default function SmokeTest() {
  const [status, setStatus] = useState<Status>(Status.Idle)
  const abortRef = useRef<AbortController | null>(null)

  useEffect(() => {
    return () => {
      abortRef.current?.abort()
    }
  }, [])

  const handleClick = () => {
    abortRef.current?.abort()
    const controller = new AbortController()
    abortRef.current = controller

    setStatus(Status.Loading)
    const service = new ApiService()
    service
      .fetchData(controller.signal)
      .then(() => setStatus(Status.Idle))
      .catch((err: unknown) => {
        if (err instanceof Error && err.name === 'AbortError') return
        setStatus(Status.Error)
      })
  }

  return (
    <div>
      <p>Status: {status}</p>
      {status === Status.Error && (
        <p role="alert" style={{ color: 'red' }}>
          API call failed — check Oxc transform output.
        </p>
      )}
      <button onClick={handleClick}>Test API</button>
    </div>
  )
}

API endpoint configuration: The smoke test uses import.meta.env.VITE_API_URL with a fallback to '/api/health'. Create a .env file with VITE_API_URL=http://localhost:3000/api/health (or your actual endpoint) to avoid 404 errors during testing.

Decorator configuration: If you use this smoke test's decorator example, ensure your tsconfig.json includes "experimentalDecorators": true to enable legacy Stage 1 decorators. Without this flag, TypeScript 5.x will expect Stage 3 decorator syntax, which uses a context: ClassMethodDecoratorContext parameter instead of the three-argument (target, key, descriptor) signature shown above.

Edit the component's JSX while the dev server is running and confirm that the changes appear instantly without a full page reload. If Fast Refresh fails, verify that @vitejs/plugin-react is at its Vite 8-compatible version and that the oxc.jsx configuration matches the project's JSX runtime setting.

Common transform errors at this stage include: unresolved path aliases (update vite-tsconfig-paths), decorator transform failures (enable oxc.typescript.decorators and set experimentalDecorators in tsconfig), and enum-related issues discussed in the next section.

Handling Common Migration Issues

TypeScript Enum and Const Enum Behavior

esbuild handled TypeScript enums by inlining numeric values and preserving string enum patterns. Oxc's transformer aligns more closely with TypeScript's own emit behavior. Oxc does not inline const enum declarations across file boundaries when isolatedModules is enabled, and Vite sets isolatedModules: true implicitly because it transforms files individually without full-program type information. The recommended pattern is to avoid const enum in cross-file usage and use regular enum or plain object literals with as const:

// Preferred pattern for cross-file constants — avoids name collision
export const Status = {
  Idle: 'idle',
  Loading: 'loading',
  Error: 'error',
} as const

// Renamed type to avoid shadowing the const in strict linting environments
export type StatusValue = (typeof Status)[keyof typeof Status]

Custom JSX Factories and Pragma Comments

Projects that use a custom JSX factory (such as Preact's h function or a custom createElement wrapper) and have not migrated to the automatic JSX runtime need to update both tsconfig.json and vite.config.ts:

// tsconfig.json — for projects using classic JSX runtime
// Note: "jsx": "react" is the TypeScript compiler option for the classic runtime;
// it must match oxc.jsx.runtime: 'classic' in vite.config.ts
// Note: "moduleResolution": "bundler" requires TypeScript >= 5.0;
// use "node16" for TypeScript 4.x projects.
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "strict": true,
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment",
    "isolatedModules": true,
    "moduleResolution": "bundler",
    "skipLibCheck": true
  }
}

For projects already on the automatic runtime, ensure tsconfig.json has "jsx": "react-jsx" and "jsxImportSource": "react" set, matching the oxc.jsx configuration in vite.config.ts. Mismatches between these two files are the most common source of JSX-related build failures after migration.

Mismatches between these two files are the most common source of JSX-related build failures after migration.

CSS Modules and Asset Handling Changes

The CSS pipeline in Vite 8 is unchanged from Vite 7, as CSS processing flows through PostCSS and Lightning CSS rather than through esbuild or Oxc. CSS Modules, @import resolution, and asset URL handling work identically. The one exception: CSS-in-JS libraries that relied on esbuild's transform for .css.ts files may need plugin updates.

Monorepo and Workspace Considerations

In monorepo setups, each package with its own vite.config.ts must be updated independently. Shared configuration files (often in a root packages/config directory) should update the esbuild block to oxc in a single location. Dependency pre-bundling now uses Oxc's resolver, which may resolve packages differently in workspace symlink scenarios. Running vite --force after migration clears the pre-bundle cache and forces Oxc to rebuild the dependency graph. Note: --force triggers a full dependency re-bundle on next startup, which may take significantly longer than normal.

Performance Comparison: esbuild vs Oxc in a React Production Build

Benchmark Setup

The following performance figures are directional estimates based on early reports from the Vite development cycle. They have not been independently verified and should not be cited as authoritative benchmarks. The test application is described as having approximately 500 components, 50 routes, and standard production dependencies including React Router, Zustand, and TanStack Query, but no reproducible benchmark repository or methodology has been published.

Results

MetricVite 7 (esbuild)Vite 8 (Oxc)Change
Dev server cold start~800ms~450ms~44% faster
HMR update (single file)~40ms~25ms~38% faster
Production build (cold)~12s~8s~33% faster
Production bundle size1.42 MB1.42 MBNo change

Bundle sizes are effectively identical because Oxc performs the same semantic transforms as esbuild. The estimated performance gains come from tighter integration that reduces overhead between Vite's Node.js process and the transformer. Independent benchmarking on your own codebase is recommended before drawing conclusions.

Complete Migration Checklist

The following checklist covers every step of the migration. It is formatted as a GitHub issue template for direct use in project tracking.

---
name: Vite 8 Migration
about: Track migration from Vite 7 (esbuild) to Vite 8 (Oxc)
title: "chore: migrate to Vite 8 / Oxc"
labels: tooling, migration
---

## Vite 8 Migration Checklist

- [ ] 1. Back up current `vite.config.ts` and `package.json`
- [ ] 2. Verify Node.js version is >= 18.0.0 (`node --version`)
- [ ] 3. Upgrade Vite to v8.x (`vite@^8.0.0`)
- [ ] 4. Upgrade `@vitejs/plugin-react` to Vite 8-compatible version (or `plugin-react-swc` equivalent) — verify version at npm registry
- [ ] 5. Remove or migrate `esbuild` config block in `vite.config.ts`
- [ ] 6. Add and configure `oxc` options (JSX runtime, TypeScript settings, target) — verify config key name against Vite 8 TypeScript types
- [ ] 7. Migrate `drop: ['console', 'debugger']` to the Vite 8 minifier configuration (do not skip — production safety concern)
- [ ] 8. Update `tsconfig.json` if using custom JSX factories, pragma settings, or legacy decorators (`experimentalDecorators: true`)
- [ ] 9. Audit and update third-party Vite plugins for v8 compatibility — check each plugin's changelog/peerDependencies
- [ ] 10. Run dev server with `vite --force` — verify HMR and React Fast Refresh work correctly
- [ ] 11. Run production build — verify output and no transform errors
- [ ] 12. Verify console.log is stripped from production bundle: `grep -rn 'console.log' dist/assets/*.js` (expect no output)
- [ ] 13. Run full test suite against production bundle
- [ ] 14. Compare bundle sizes and build times against Vite 7 baseline
- [ ] 15. Update CI/CD pipeline configuration (Node.js version, cache paths)
  - [ ] Ensure CI uses `pnpm install --frozen-lockfile` (not `--no-frozen-lockfile`)
  - [ ] Ensure CI caches `node_modules/.vite` (Vite's pre-bundle cache) and invalidate cache after this migration
- [ ] 16. Update team documentation and ADRs

Looking Ahead: Oxc, Rolldown, and the Future Vite Toolchain

Rolldown as the Future Bundler

Oxc is one half of Vite's long-term toolchain strategy. The other half is Rolldown, a Rust-based bundler designed as a drop-in replacement for Rollup. Oxc and Rolldown share parser and resolver infrastructure, meaning that once Rolldown replaces Rollup as Vite's production bundler, the entire pipeline from source file parsing through bundled output will run in Rust with zero JavaScript serialization boundaries. As of this writing, Rolldown integration has been discussed for a future Vite release; no firm timeline has been committed. Monitor https://github.com/vitejs/vite/discussions for RFC updates.

What to Watch in the React + Vite Ecosystem

Two developments are worth tracking. React Compiler (the automatic memoization compiler, developed internally as "React Forget") will need a Vite plugin that integrates with Oxc's transform pipeline rather than Babel's. React Server Components (RSC) and the associated build pipeline for splitting server and client module graphs will depend on bundler-level support that Rolldown is being designed to provide. Projects planning to adopt these React features should monitor the Vite and Rolldown RFCs for compatibility updates.

Wrapping Up the Migration

Drop the migration checklist into a GitHub issue to track progress. The primary work involves replacing the esbuild configuration block with oxc, upgrading official plugins to their Vite 8-compatible versions, and verifying that TypeScript edge cases like enums and decorators transform correctly. For a standard React setup, this typically means changing fewer than 20 lines of config. Projects with custom JSX factories or extensive esbuild-specific configuration will require more deliberate attention.

For authoritative details beyond what this guide covers, the official Vite 8 migration guide and Oxc documentation are the definitive references.

SitePoint TeamSitePoint Team

Sharing our passion for building incredible internet things.

© 2000 – 2026 SitePoint Pty. Ltd.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.