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.
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
| Webpack | Vite | Turbopack (what runs here) | |
|---|---|---|---|
| Dev strategy | Bundles the whole graph before first serve | Serves native ESM, transforms on request | Incremental, demand-driven compile |
| Cold start vs. codebase size | Grows roughly linearly | Near-constant | Near-constant |
| Production bundler | Webpack | Rollup (historically), Rolldown going forward | Turbopack |
| Config file you write | webpack.config.js, usually long | vite.config.ts, usually short | None — the framework owns it |
| Plugin interface | JavaScript functions | JavaScript functions | Rust core; JS plugins cross a boundary |
| Written in | JavaScript | Go/JS (esbuild) + Rust (Rolldown) | Rust |
| Who picks your defaults | You | Vite, then you | The 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
| Symptom | Cause | Fix |
|---|---|---|
Tables render as literal | characters in MDX | remark-gfm passed as an imported function; Turbopack's Rust pipeline dropped it silently | Pass plugins as strings: remarkPlugins: ["remark-gfm"] |
| Local dev blocked by CSP, production fine | Dev-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 @theme | Tailwind v4 @theme changes do not hot-reload under Turbopack | Restart the dev server |
| Build is "slow" but the compile step is fast | The time is in typechecking or static generation, not bundling | Measure the split before switching tools — a bundler swap cannot help either |
| Vite dev works, production build breaks | Dev serves native ESM unbundled; production goes through the real bundler | Test 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.
