Skip to main content
ASoc
Comparison

Vite vs. Webpack: 488 Pages Built by a Repo Running Neither

Most comparisons assume you pick a bundler. On a framework you inherit one — and the two config lines this codebase writes anyway are where the real difference shows.

The ASoc Team9 min read

Vite and Webpack are both build tools, but they disagree about development: Webpack bundles your whole app before serving anything, while Vite serves native ES modules and bundles only for production. If you are on a framework, though, the choice may already be made for you — this repository contains no vite.config.ts and no webpack.config.js, and still builds 488 pages in 32.6 seconds.

The comparison most posts skip

Almost every "Vite vs Webpack" article assumes you are choosing a bundler directly. That is true for a standalone React SPA. It is not true for the large fraction of React work that happens inside Next.js, Remix, Astro or SvelteKit, where the framework owns the build and the bundler becomes an implementation detail you inherit. This comparison at least stays inside one layer, which is more than can be said for Vite vs. Vue — a search people run constantly for two tools that are not alternatives at all.

This storefront is the second case, and the evidence is what the repository does not contain:

$ ls vite.config.ts vite.config.js webpack.config.js webpack.config.ts
ls: cannot access 'vite.config.ts': No such file or directory
ls: cannot access 'vite.config.js': No such file or directory
ls: cannot access 'webpack.config.js': No such file or directory
ls: cannot access 'webpack.config.ts': No such file or directory

Four checks, four misses. Neither vite nor webpack appears in package.json either — the full dependency list is 13 runtime packages and 13 dev packages, and the build tool is not among them, because it arrives inside next itself:

▲ Next.js 16.2.9 (Turbopack)

So the honest framing of this comparison is three-way, not two-way: Vite, Webpack, or a bundler your framework selects and configures on your behalf.

What each one actually does differently

WebpackViteTurbopack (what runs here)
Dev strategyBundles the whole graph before first serveServes native ESM, transforms on requestIncremental, demand-driven compile
Cold start vs. codebase sizeGrows roughly linearlyNear-constantNear-constant
Production bundlerWebpackRollup (historically), Rolldown going forwardTurbopack
Config file you writewebpack.config.js, usually longvite.config.ts, usually shortNone — the framework owns it
Plugin interfaceJavaScript functionsJavaScript functionsRust core; JS plugins cross a boundary
Written inJavaScriptGo/JS (esbuild) + Rust (Rolldown)Rust
Who picks your defaultsYouVite, then youThe framework

The row that matters most for a framework user is the last one. A Vite or Webpack project trades configuration work for control. A framework-bundled project trades control for not writing that config at all — and, as the next two sections show, the trade is not perfectly clean.

The measured build

This is a real run of npm run build on this repository, not a benchmark written for the article:

▲ Next.js 16.2.9 (Turbopack)

  Creating an optimized production build ...
✓ Compiled successfully in 12.0s
  Running TypeScript ...
  Finished TypeScript in 8.1s ...
  Collecting page data using 3 workers ...
✓ Generating static pages using 3 workers (488/488) in 8.6s
  Finalizing page optimization ...

Total wall clock: 32.6 seconds. The interesting part is the split. Only 12.0s of it is compilation — the part a bundler comparison is actually about. 8.1s is TypeScript, which is the same cost under any of the three tools, and 8.6s is rendering 488 pages to HTML, which is not a bundling activity at all.

That is the practical caveat behind every bundler benchmark you will read: cold-start and HMR numbers measure the slice of your build that the bundler owns, and on a content-heavy site that slice is roughly a third of the total. Switching bundlers cannot touch the other two thirds.

The CSS output is a single file for the entire site:

$ stat -c '%s %n' .next/static/chunks/*.css
78049 .next/static/chunks/3eehyjxxz6pqi.css
$ gzip -c .next/static/chunks/3eehyjxxz6pqi.css | wc -c
13958

78,049 bytes raw, 13,958 gzipped, covering 26 static routes, 3 SSG route families that expand to 488 prerendered pages, and 8 dynamic routes.

Where the bundler leaks into your config anyway

"The framework owns the build" is true right up until it isn't. Two lines in this repository exist purely because of which bundler runs underneath, and both were found the hard way.

1. MDX plugins must be strings, not imports. The blog compiles MDX at build time. The plugin list looks like this:

// next.config.ts
const withMDX = createMDX({
  options: {
    remarkPlugins: ["remark-gfm"],
    rehypePlugins: ["rehype-slug"],
  },
});

Those are string names, not imported function references, and that is not a style preference. Turbopack runs the MDX pipeline in Rust and cannot receive a JavaScript function across that boundary. Passing the imported plugin instead builds successfully and silently drops the plugin — the worst failure mode available, because nothing errors. The visible symptom was every comparison table in the blog rendering as paragraphs of literal | characters, since MDX defaults to CommonMark and CommonMark has no table syntax. Under Webpack, where the loader chain is JavaScript end to end, the imported reference works fine. The bundler's implementation language became an API constraint.

2. The Content-Security-Policy needs 'unsafe-eval' in development.

// next.config.ts
// Next.js/Turbopack dev (Fast Refresh) evaluates modules via eval; production
// (`next start`) is eval-free. Allow 'unsafe-eval' ONLY in development so local
// HMR works without weakening the shipped production policy.
const scriptSrcEval =
  process.env.NODE_ENV === "development" ? " 'unsafe-eval'" : "";

Hot module replacement has to get new module code into a running page, and the dev server does it with eval. That is a property of the dev-server strategy, not of Next.js — Webpack's HMR does the same thing, and Vite's is the reason strict-CSP dev setups need a comparable exception. The production policy stays eval-free because the production build is a real bundle with no HMR in it. If you ship a strict CSP, this is a line you will write under any of the three tools.

There is a third, smaller one worth knowing if you use Tailwind v4: editing the @theme block in globals.css does not hot-reload under Turbopack. New utilities silently fail to generate until you restart npm run dev. Same class of problem — a Rust-side pipeline that does not watch what the JavaScript-side one did.

So which should you choose?

The decision collapses quickly:

  • Building a standalone SPA or library today? Vite. Faster cold start, far shorter config, and the ecosystem has largely moved. Webpack's advantage was never raw speed.
  • Maintaining a large existing Webpack build? Migration cost is real and the config flexibility is genuinely unmatched. "Vite is faster" does not by itself justify rewriting a working build pipeline.
  • On Next.js, Remix, Astro or SvelteKit? You are not choosing. Spend the effort on what the framework does expose — in this build, the 8.6s of static generation and the 8.1s of typechecking are both larger line items than compilation, and both are addressable.

The thing to actually check before adopting any of them is the plugin boundary. That is where this codebase lost time, and it is invisible in every cold-start benchmark.

Troubleshooting

SymptomCauseFix
Tables render as literal | characters in MDXremark-gfm passed as an imported function; Turbopack's Rust pipeline dropped it silentlyPass plugins as strings: remarkPlugins: ["remark-gfm"]
Local dev blocked by CSP, production fineDev-server HMR uses eval; your policy has no 'unsafe-eval'Add 'unsafe-eval' to script-src for NODE_ENV === "development" only
New Tailwind utility class does nothing after editing @themeTailwind v4 @theme changes do not hot-reload under TurbopackRestart the dev server
Build is "slow" but the compile step is fastThe time is in typechecking or static generation, not bundlingMeasure the split before switching tools — a bundler swap cannot help either
Vite dev works, production build breaksDev serves native ESM unbundled; production goes through the real bundlerTest against a production build in CI, not just the dev server

Frequently asked questions

Is Turbopack just Webpack rewritten in Rust? No. Both are made by Vercel and Turbopack is the successor in Next.js, but it is a different architecture — incremental and demand-driven rather than graph-bundling upfront — and, as shown above, its Rust core changes what a plugin can be. That is why the MDX plugin fix was necessary rather than optional.

Does Vite use Rollup or esbuild? Both, historically, for different phases: esbuild for dependency pre-bundling and TypeScript transforms, Rollup for the production bundle. Rolldown, a Rust Rollup-compatible bundler, is the direction of travel. This is the same "which engine, at which phase" question the table above answers for Turbopack.

Can I use Vite with Next.js? Not as a replacement for the build pipeline. Next.js owns compilation, routing, and rendering together; you would be adopting a different framework, not swapping a bundler. If you want Vite plus React with file routing, that is React Router v7 or TanStack Start territory.

Do the templates in this catalog need a bundler config? No. Every Next.js edition builds with the framework's own pipeline, exactly as shown above — the next.config.ts in each one carries application concerns like headers and MDX, not bundler configuration.

Templates in this post

ASoc Remit (a payments-platform marketing site), ASoc Script (an AI copywriting SaaS landing page) and ASoc Seeker (an AI keyword-research landing page) all build the same way this storefront does: next build, no bundler config to write or maintain.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For more on what this build actually emits, see Next.js Bundle Analyzer, A Next.js CI Pipeline in 23 Lines and React Landing Pages: Half the HTML Isn't the Page.

Keep reading

Comparison8 min read

Vitest vs. Jest: 11 Lines of Config and 331 Tests in 2.48s

Measured on this repo: 31 files, 331 tests, 2.48s — and the `environment 3ms` line that explains most of the Vitest-versus-Jest gap.

Read more
Comparison10 min read

Webflow vs Next.js for a Landing Page: How to Actually Decide

The launch-speed argument mostly disappears once you compare template to template. What is left is who edits the copy, what each locks in, and three cases where Webflow wins.

Read more
Comparison12 min read

WordPress vs Next.js for a Marketing Site: The Honest Comparison

Most comparisons pit a WordPress theme against a from-scratch Next.js build. Compare theme to template instead and the real trade turns out to be who edits the copy.

Read more