Migrating to Vite, Vitest, Oxlint, and OxfmtHow migrating from Webpack, Jest, and ESLint to Vite, Vitest, Oxlint, and Oxfmt improved performance by 10x

2026-05-139 min read

Migrating from Webpack to Vite (and Everything Else That Came With It)

tl;dr:

I migrated this website from Webpack, Jest, ESLint, and Prettier to a modern stack built on Vite, Vitest, Oxlint, and Oxfmt. The result was dramatically faster builds, smaller bundles, and a much smoother development experience overall.

For years, this website ran on a fairly traditional frontend stack: Webpack for bundling, Jest for testing, and ESLint + Prettier for linting and formatting.

It worked. Mostly.

But over time, the small annoyances started adding up:

  • slower rebuilds
  • heavier tooling
  • linting taking longer than expected
  • test runs feeling increasingly sluggish
  • development feedback loops getting interrupted

None of these problems were catastrophic on their own, but together they made the project feel heavier than it should.

So I decided to modernize the entire toolchain.


Why I Moved Away from Webpack

Webpack served the ecosystem incredibly well for years, but the development experience started to feel dated compared to newer tooling.

Even on a relatively small project, rebuild times were no longer instant, and HMR performance became noticeable enough to interrupt flow.

Vite changed that immediately.

Instead of bundling the entire application during development, Vite relies on native ES modules and only transforms what's actually needed. That one architectural decision makes a huge difference.

# Before (Webpack)
File change → rebuild → refresh
~3–5 seconds

# After (Vite)
File change → instant HMR
<50ms

The biggest improvement wasn't the raw number itself — it was how much more responsive development started to feel.

You stop waiting. You stay focused.

Production builds also became simpler. Vite uses Rollup internally, so tree-shaking and code splitting worked with minimal configuration.


Moving to Static Site Generation

This website already supported SSG before the migration through a collection of Webpack plugins.

Ironically, setting it up with Webpack was actually simpler in some ways.

The difference is that the old implementation relied heavily on plugin-specific behavior and custom configuration, while the Vite version required building a more explicit SSR/SSG pipeline tailored to the project.

Even though the migration took more effort, the end result feels cleaner and easier to reason about.

The site now pre-renders pages into static HTML during build time, which improved several things at once:

  • Faster page loads
  • Better SEO
  • Better caching
  • Content that still works without JavaScript

I also integrated vite-plugin-pwa to add offline support and improve repeat visits.

One thing I particularly like about the current setup is that the rendering flow is now much more transparent. Instead of relying on layers of Webpack abstractions, the Vite-based pipeline feels closer to standard Node.js and browser behavior.

That made debugging and optimization significantly easier long term, even if the migration itself was more involved.


Custom Vite Plugins

One of the most enjoyable parts of the migration was building custom Vite plugins tailored to the project.

SVG Sprite Plugin

This plugin automatically generates SVG sprites during build time and watches for icon changes during development.

It also clears SSR caches whenever icons are updated, which avoids stale assets during local development.

App Plugin

Handles the SSR/SSG pipeline and pre-rendering logic for static pages.

Sitemap Generator

Automatically generates sitemap.xml from application routes with metadata like:

  • lastmod
  • changefreq
  • priority

This removed a surprising amount of manual maintenance.


Replacing Jest with Vitest

This was probably the smoothest migration of the entire stack.

Vitest feels like what modern frontend testing should be: lightweight, fast, and tightly integrated with the build system.

Since its API is largely compatible with Jest, most of the migration involved configuration changes rather than rewriting tests.

The performance difference was immediately noticeable:

# Jest
~5 seconds

# Vitest
~300ms

Watch mode also became significantly more useful because reruns happen almost instantly.

Instead of waiting for the entire suite, Vitest only reruns what's relevant.


Replacing ESLint + Prettier with Oxlint + Oxfmt

This was the part I was most skeptical about.

ESLint has been the standard for years, and replacing it felt risky at first.

But after trying OXC, it became hard to go back.

Oxlint and Oxfmt are written in Rust and are absurdly fast compared to the traditional ESLint + Prettier setup.

# ESLint + Prettier
~8 seconds

# Oxlint + Oxfmt
~200ms

More importantly, linting stopped feeling like a separate task.

I now run checks constantly because they're effectively instant.

That changes how you work.


Performance Improvements Beyond Tooling

The migration also became an opportunity to clean up several performance issues across the site.

Some of the smaller improvements included:

  • Preconnect and preload hints for CSS and JS assets
  • Responsive portrait images with optimized sizes
  • Delayed Service Worker registration to reduce main-thread work
  • Shared IntersectionObserver instances for scroll animations

CI/CD

The CI pipeline also benefited significantly:

  • 50% faster CI
  • Using pre-built dist artifacts reduced deployment overhead

Individually these changes are small, but together they noticeably improved page responsiveness and reduced unnecessary work during startup.


Accessibility and Progressive Enhancement

I also spent some time improving accessibility and no-JavaScript behavior during the migration.

A few examples:

  • Replaced fade-based animations with a more subtle ScrollReveal
  • Fixed checkbox labeling issues for screen readers
  • Added translated alt text for images
  • Added CSS-only fallbacks when JavaScript is disabled

One thing I care about with personal websites is that they should still function without requiring a fully hydrated JavaScript application.

This migration pushed the site closer to that goal.


Migration Challenges and Tradeoffs

The migration itself was surprisingly smooth overall, but not everything worked immediately.

ESM Friction

Moving fully into the ESM ecosystem exposed a few rough edges, especially around tooling and Node.js compatibility.

Some packages still assume CommonJS by default, which meant adjusting imports, replacing outdated dependencies, and occasionally working around SSR-specific issues.

Most of these problems were small individually, but they added up during the migration process.

Vite SSR Edge Cases

SSR and SSG with Vite are much simpler than configuring Webpack manually, but there were still some gotchas around:

  • dynamic imports
  • browser-only APIs
  • cache invalidation
  • asset handling during prerendering

The custom Vite plugins helped solve most of these problems cleanly.

Oxlint Ecosystem Maturity

Oxlint is incredibly fast, but the ecosystem is still younger than ESLint's.

A few specialized ESLint plugins don't have direct equivalents yet, so there were some compromises in rule coverage.

For this project, the performance gains were absolutely worth it, but it's still something to consider for larger teams or highly customized lint setups.


Real-World Performance Results

The improvements weren't just theoretical — they were noticeable immediately during daily development.

MetricBeforeAfterImprovement
Dev server start~3s~1s3x faster
HMR updates~500ms< 50ms10x faster
Test suite~5s~300ms15x faster
Lint + format~8s~200ms40x faster
CI pipeline~2min~1min50% faster

The biggest difference wasn't the benchmarks themselves — it was how much smoother development became throughout the day.


Why I Stayed with Preact

This project already used Preact before the migration, and moving to Vite made that setup even better.

Preact keeps the bundle small while still offering a React-like developer experience. Combined with Vite's fast development server and Rollup optimizations, the overall setup feels extremely lean.

For a personal website, that balance between simplicity, performance, and maintainability matters a lot more to me than adding additional abstraction layers.


What I Removed

One underrated benefit of modern tooling is simply having fewer dependencies to maintain.

The migration removed:

  • webpack
  • webpack-dev-server
  • jest
  • eslint
  • prettier
  • stylelint

The dependency count dropped from 47 packages to 31.

Less configuration. Less maintenance. Less noise.


Future Improvements

There are still a few things I want to improve over time:

  • Add bundle analysis visualizations
  • Introduce end-to-end performance monitoring
  • Experiment with partial hydration patterns
  • Continue reducing client-side JavaScript where possible

The migration solved a lot of problems, but it also made the architecture simpler enough that future optimizations now feel much easier to implement.


Final Thoughts

The biggest takeaway from this migration wasn't just performance.

It was reducing friction.

Fast tooling changes how development feels on a daily basis. You spend less time waiting and more time actually building things.

Modern frontend tooling has matured a lot over the last few years, and this migration made the project noticeably simpler, faster, and easier to maintain.

🏁 Thanks for reading! If you have questions or feedback, feel free to reach out.

The full source code of this site is available on GitHub.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

paulogoncalves.dev ©️ 2021 - 2026 🤘🏻