Migrating from Vite to Next.js 14 App Router: A Complete Guide
May 10, 2026 15 min read Wise Technologies Team
#Next.js#Vite#Migration#Tailwind CSS#React
Why We Migrated
Our original site was built with Vite + React — fast to develop, but terrible for SEO. No server-side rendering meant Google struggled to index our content. No automatic route generation meant manual sitemap maintenance. We chose Next.js 14 App Router for its static export capability, built-in SEO features, and file-based routing.
The App Router vs Pages Router Decision
Next.js 14 offers two routing systems. The Pages Router is mature and well-documented. The App Router is newer but offers better static generation, streaming, and nested layouts. For a marketing site with blog posts, the App Router's `generateStaticParams` and `metadata` APIs were perfect. We committed to App Router despite the smaller community knowledge base.
Tailwind CSS v4 Configuration Hell
Tailwind v4 changed everything. No more `tailwind.config.js`. Instead, you use `@import "tailwindcss"` in CSS and configure via CSS variables. The bigger issue: `@tailwindcss/vite` plugin is incompatible with Next.js. We spent hours debugging before discovering you must use `@tailwindcss/postcss` instead. Our `postcss.config.cjs` now looks like this: `module.exports = { plugins: { "@tailwindcss/postcss": {} } }`.
The ES Module Trap
Our `package.json` has `"type": "module"`. This broke Next.js because it tried to `require()` the PostCSS config. The fix: rename `postcss.config.js` to `postcss.config.cjs`. This tells Node to treat it as CommonJS regardless of the package type. A one-character fix that took two hours to find.
Static Export Configuration
Next.js 14 static export requires `output: 'export'` in `next.config.js`. But this disables API routes and dynamic rendering. For our blog, we pre-build all posts at build time using `generateStaticParams`. Each blog post becomes a static HTML file in the `dist/` folder. Images must use `unoptimized: true` because the image optimizer requires a server.
Font Loading with next/font
We replaced manual Google Fonts `` tags with `next/font/google`. This automatically optimizes fonts, creates fallback CSS, and prevents layout shift. However, not all fonts support the `latin` subset. Almarai (an Arabic font) only supports `arabic`, which caused a build error. We removed it and loaded it via CSS fallback instead.
Scroll Animation Performance
Framer Motion's useScroll hook caused severe performance issues — re-rendering the Header component on every scroll pixel. We replaced it with a native useEffect + window.addEventListener("scroll", ..., { passive: true }) pattern. This eliminated the blinking effect and reduced CPU usage by 80% during scroll.
Build and Deploy
Our build command is `next build` which outputs to `dist/` (configured via `distDir: 'dist'`). We deploy to Cloudflare Pages which serves static files globally. The entire build takes ~45 seconds and produces 16 static HTML files: home, services, blog, 8 blog posts, sitemap, and robots. Lighthouse scores: 98 Performance, 100 Accessibility, 100 Best Practices, 100 SEO. Check out our sketchbook design trend article to see how we styled this site.
Wise Technologies Team
Full-Stack Development
"Enjoyed this article? We build the tools we write about."