The Great Migration: From SPA to Framework
I remember the day I decided to move my first major project from Vite to Next.js. The Vite setup was fast, clean, and served me well for months. However, as the project grew, I hit the wall that many developers eventually face: the need for better SEO, faster First Contentful Paint, and a more robust way to handle server-side logic without maintaining a separate Express backend.
Migrating isn't just about moving files from one folder to another. It’s a shift in mental models—from a Client-Side Rendered (CSR) mindset to a pre-rendering mindset. Here is everything I wish I knew before I started that first migration.
1. It’s All About the Router
In Vite, you probably used react-router-dom. You had a single App.tsx file with a massive <Routes> block. In Next.js (especially with the App Router), the folder structure is your router.
I spent hours trying to make my existing router work inside Next.js before realizing I was fighting the framework.
Lesson Learned: Don't try to shim react-router-dom into Next.js. Delete it. Move your components into app/page-name/page.tsx. Use next/link instead of Link and useRouter from next/navigation instead of useNavigate.
2. Defaulting to Server Components
By default, every file in the Next.js App Router is a React Server Component (RSC). This is a huge shift. In Vite, your components could all use useEffect, useState, and browser APIs like window or localStorage without a second thought.
When I first migrated, my build failed with hundreds of errors. Why? Because I hadn't added the "use client"; directive to components that relied on state or browser-only APIs.
Strategy: Keep your data fetching logic in Server Components and push your interactivity (buttons, forms, state) as far down the component tree as possible. If you find the manual restructuring of these components overwhelming, you can use specialized tools like ViteToNext.AI to automate the heavy lifting of converting your Vite structure into Next.js compatible layouts and components.
3. Data Fetching Reimagined
In my Vite app, I had an useEffect hook that pulled data from an API. It worked, but it resulted in a "loading spinner" experience for users.
Next.js allows you to fetch data directly in your component using async/await.
// Vite style (CSR)
useEffect(() => {
fetch('/api/data').then(res => setData(res.json()))
}, [])
// Next.js style (Server Component)
const data = await fetch('https://api.example.com/data').then(res => res.json())
This change significantly improved my SEO because the data was already there when the HTML reached the browser.
4. Handling Environment Variables
Vite uses import.meta.env.VITE_APP_KEY. Next.js uses process.env.NEXT_PUBLIC_APP_KEY.
I wasted thirty minutes wondering why my API keys were undefined because I forgot that Next.js requires the NEXT_PUBLIC_ prefix for variables intended for the browser. Variables without this prefix are only accessible on the server, which is actually a great security feature I didn't have by default in my Vite SPA.
5. CSS and Global Styles
If you were using Tailwind in Vite, the transition is smooth. However, if you were using plain CSS or SCSS, you need to be careful. Next.js is strict about where you can import global CSS.
- Global CSS goes in your root
layout.tsx. - Component-specific CSS should use CSS Modules (
filename.module.css) to avoid style bleeding.
I had a lot of overlapping styles in my Vite project that I had to manually scope into modules during the migration. Plan your CSS strategy before you start moving files.
6. The "Window is not defined" Error
Since Next.js attempts to pre-render your code on the server, any code that touches the window or document object will crash during the build.
// This will fail in Next.js during SSR
const width = window.innerWidth;
// Use this instead
useEffect(() => {
const width = window.innerWidth;
}, []);
Anything inside a useEffect is safe because it only runs after the component has mounted in the browser.
Conclusion
Migrating from Vite to Next.js is more than a technical upgrade; it's an architectural one. While the learning curve can be steep—especially regarding Server Components and the file-based router—the benefits of performance and developer experience represent a massive leap forward for any production application. Take it one folder at a time, embrace the server-first mindset, and don't be afraid to use automated tools to speed up the process.
Further reading: Accelerate your migration with ViteToNext.AI
Top comments (0)