Migrating from React + Express to Next.js: Was It Worth It?
I migrated all my products from React + Express to Next.js. One framework for frontend, backend, and deployment transformed my productivity.

In 2019, my stack was React on the frontend and Express on the backend. Two separate applications. Two deployment pipelines. Two sets of configuration. Two packages to manage.
The stack worked. But every time I started a new project, I spent the first day setting up the same boilerplate: React app, Express server, CORS configuration, environment variables, deployment scripts. And every time I context-switched between products, I had to remember which Express routes served which React views.
Next.js promised to eliminate this friction: one application for frontend and backend. Server-side rendering built in. API routes next to the pages that call them. File-system routing that eliminates route configuration. One deployment to Zeit Now.
The promise was accurate. The migration was worth every hour.
Before Next.js
My pre-Next.js setup for each product:
project/
client/ # Create React App
src/
components/
pages/
services/ # API call functions
server/ # Express
routes/
middleware/
models/
shared/ # Shared types and utilities
The client called the server through HTTP requests. CORS headers allowed cross-origin requests during development. In production, the server served the built React bundle and the API routes.
This setup had specific pain points:
Two build processes. The client needed npm run build for the React bundle. The server needed its own build (or no build for plain JavaScript). Deployment required building both and ensuring they were in sync.
Development server coordination. The client ran on port 3000. The server ran on port 5000. A proxy configuration forwarded API requests from the client dev server to the server. When the proxy broke (which happened more often than it should), API calls silently failed.
Environment variable duplication. Some environment variables needed to be available on both the client and the server. Keeping them in sync across two .env files was a manual, error-prone process.
Shared code complexity. Types, utilities, and constants that both sides needed lived in a shared directory. Importing from shared into client required build tool configuration that varied between tools and broke between upgrades.
None of these problems were insurmountable. Each one was a small tax on productivity. Together, they compounded into a meaningful drag on shipping speed.
The Migration
Migrating an Express + React application to Next.js involved:
Converting pages. React components became Next.js pages. The file-based routing replaced react-router configuration. Each page file became both the route definition and the component, with no separate router file.
Converting API routes. Express routes became Next.js API routes. The handler signature changed (from (req, res) to Next.js conventions), but the logic was identical. Each API file became a self-contained endpoint.
Eliminating the proxy. With frontend and backend in the same application, there's no CORS and no proxy. API calls from pages to API routes are same-origin. The entire class of proxy-related bugs disappeared.
Moving to file-based structure. Instead of client/pages/ and server/routes/, everything lived in Next.js's directory structure. Pages and their corresponding API routes were co-located. The dashboard page and the dashboard API route lived in adjacent directories.
Server-side rendering. Data fetching that previously happened on the client (loading spinners, useEffect hooks, loading states) moved to the server. Pages arrived with data. The user experience improved immediately, with no flash of empty content.
The migration took a weekend per product. The productivity improvement was permanent.
What Changed
Cold start time dropped to zero. A new Next.js project with API routes, server rendering, and TypeScript takes fifteen minutes to set up. The equivalent Express + React setup took a day. For someone who starts new products frequently, this time savings was enormous.
Context switching became trivial. Every product has the same structure. Open the project, navigate to the page directory, start editing. No remembering which product uses which routing convention or which API naming pattern.
Deployment became invisible. Push to Git. Zeit Now deploys automatically. Preview URL for every branch. Production deployment for every merge to main. No build scripts. No deployment configuration. No SSH-into-server-and-pull-latest.
SEO improved immediately. Server-rendered pages gave search engines complete HTML instead of an empty shell with JavaScript. The SEO impact was measurable within weeks: better indexing, better ranking for content-heavy pages.
Performance improved. Server-rendered pages load faster for users because the browser receives complete HTML instead of a blank page that fetches data. First Contentful Paint improved across every migrated product.
The Tradeoffs
Next.js isn't without costs:
Vendor coupling. Next.js is maintained by Zeit. While it's open source and deployable anywhere, the best experience is on Zeit Now. I accepted this coupling because Zeit's deployment experience is worth the dependency.
Framework evolution. Next.js is still evolving rapidly. Migrations between Next.js versions can add work as new patterns and conventions are introduced. Staying current requires ongoing investment.
Less control over the server. Express gives you full control over the HTTP server. Next.js abstracts the server away. For most web application needs, this abstraction is fine. For unusual requirements (WebSockets, custom middleware chains, non-HTTP protocols), the abstraction can be limiting.
Build times. Next.js build times grow with application size. For large applications with hundreds of pages, builds can take minutes. This is acceptable for production deployments but can slow development iteration.
These tradeoffs are real but manageable. The productivity gains far outweigh the costs for my use case: solo founder building and maintaining multiple web applications.
The Advice
If you're maintaining separate frontend and backend applications and you're a solo founder or small team, migrate to Next.js. The migration cost is measured in days. The productivity improvement is measured in years.
If you're starting a new project, start with Next.js. There's no reason to maintain separate applications for a web product that could be unified.
The decision to use Next.js is the last framework decision I made. Since then, I've started every project the same way: npx create-next-app. The consistency is the feature.
Enjoyed this article?
I write about building products, AI, aviation, and the journey of entrepreneurship. Follow along for more.
Keep reading

Getting Started with Allem SDK: React Hooks for AI, Forms & Auth
Allem SDK is a collection of React hooks for AI chat, form validation, authentication, analytics, and utilities. Here is how to install and use it.

Getting Started with Allem UI: React & React Native Components
Allem UI is an accessible component library for React and React Native with 44+ components, dark mode, and Tailwind CSS v4. Here is how to install and use it.

The Agento Suite: Building 6 AI Products in Parallel
In 2026, I launched six AI products across legal tech, travel, healthcare, and developer tools. Here is the architecture and playbook for building in parallel.