I already had a React app running, and I wanted Next.js on top of it. I didn’t want to delete the folder and start again. If that’s where you are right now, this post is for you.
You will learn how to install Next.js in an existing project, step by step, without breaking what already works.
What this post covers: Next.js 16 with the App Router, using npm. It works the same with pnpm, yarn, and bun, and I’ll show those commands too. It does not cover the Pages Router or a full React Router migration.
One thing before you start. Don’t run create-next-app inside your existing folder. It’s built to create a fresh project, and it will either make a nested folder or fight with the files you already have. The manual install is the safe path, and it’s only a few steps.
Step 1: Check your Node version first
Next.js 16 has a minimum Node version, and this is where a lot of installs fail before they begin.
node -v
# v20.9.0 or higher
Next.js 16 needs Node.js 20.9 or later. If your terminal prints something lower, update Node first. I use nvm for this, so switching versions takes one command and doesn’t touch the rest of my system.
For example, you can see:
nvm install 22installs a supported version.nvm use 22switches your current terminal to it.node -vconfirms it worked.
Step 2: Install Next.js in your existing project
This is the actual install. Three packages, one command.
npm install next@latest react@latest react-dom@latest
Using a different package manager? Pick your line:
pnpm add next@latest react@latest react-dom@latest
yarn add next@latest react@latest react-dom@latest
bun add next@latest react@latest react-dom@latest
Here’s what I just did. I installed next itself, plus react and react-dom, which Next.js renders with. If your project already has React, npm will update it to the latest version instead of adding a duplicate. That’s normally what you want, but check the version bump in your diff before committing.
Step 3: Add the Next.js scripts to package.json
Next.js won’t run until your scripts point at it. Open package.json and add these.
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"lint:fix": "eslint --fix"
}
}
If your project already has a dev or build script from Vite or a bundler, rename the old ones instead of deleting them. I use dev:vite and build:vite while I’m testing, so I can jump back if something breaks.
One change worth knowing about: next lint was removed in Next.js 16. Linting now runs through the ESLint CLI directly, which is why the script above is just eslint. If you’re coming from an older project, there’s a codemod for it:
npx @next/codemod@canary next-lint-to-eslint-cli .
Also note that Turbopack is the default bundler now. If something in your setup needs Webpack, run next dev --webpack instead.
Step 4: Create the app folder
Next.js uses file-system routing. No routes exist until the folders exist, so this step is not optional.
Create an app folder in your project root, then add layout.tsx inside it:
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Then add a home page next to it:
// app/page.tsx
export default function Page() {
return <h1>Hello, Next.js!</h1>
}
Let me walk through what these two files do. layout.tsx is the root layout, and it’s required. It has to contain the <html> and <body> tags, because Next.js does not render them for you. page.tsx is the page shown at /. Both files render together when someone visits your root URL.
If you keep your source inside a src folder, put the app folder at src/app instead. Next.js supports both, but pick one. Having app and src/app at the same time will confuse the router.
Step 5: Update .gitignore and run the dev server
Next.js writes build output that should never reach your repo.
# .gitignore
.next
next-env.d.ts
Now start it:
npm run dev
Open http://localhost:3000 and you should see your heading. That’s the whole install. Your existing files are still sitting there untouched, they’re just not wired into the router yet, which is the next step.
Step 6: Add TypeScript if your project doesn’t have it
You don’t install anything for this. Rename one file to .ts or .tsx and run the dev server.
npm run devNext.js sees the TypeScript file, installs the dependencies it needs, and writes a tsconfig.json with sensible defaults. I like this because I don’t have to remember which @types packages to add.
If you’re on VS Code, switch to the workspace TypeScript version so the Next.js plugin loads. Open the command palette, search “TypeScript: Select TypeScript Version”, and pick “Use Workspace Version”. You get better autocomplete on Next.js APIs after that.
Step 7: Move your existing React app in
This is the part people search for after the install works. You have a Vite or CRA app, and you want it rendering inside Next.js without rewriting your routes today.
The trick is one catch-all route that hands everything to your existing app. Create app/[[...slug]]/page.tsx:
// app/[[...slug]]/page.tsx
import '../../index.css' // your existing global stylesheet
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
Then the client wrapper, in the same folder:
// app/[[...slug]]/client.tsx
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false }) //your old root component
export function ClientOnly() {
return <App />
}
Here’s what’s happening. [[...slug]] is an optional catch-all segment, so every URL in your app lands on this one page. The page.tsx file stays a Server Component and imports your global CSS. The client.tsx file carries the 'use client' directive and loads your old App component with ssr: false, which keeps it client-only exactly like it was under Vite.
Two more things to change while you’re here:
- Rename your env variables from
VITE_toNEXT_PUBLIC_for anything used in the browser. - Static image imports now return an object, not a string. So
<img src={logo} />becomes<img src={logo.src} />.
Which method should you use to install Next.js in an existing project?
There are three real paths, and the right one depends on what your folder currently holds.
- Manual install. Your project has a
package.jsonand some React components, but no bundler you care about. Follow steps 1 to 6 above and you’re done in ten minutes. - Manual install plus a catch-all route. Your project is a working Vite or CRA SPA with its own router. Add step 7. You get a running Next.js app first, then migrate routes one at a time.
- Fresh app, then copy your code across. Your existing setup is old, has a broken build, or carries config nobody remembers writing. Run
npx create-next-app@latest my-appin a new folder and move your components over.
I use the manual install for almost everything. It keeps your git history intact, and every change is one line you can read in a diff. I only reach for option 3 when the old config is more work to untangle than the components are to copy.
If you want more Next.js posts like this one, there are a few on my blog covering sitemaps, metadata, and deploy setup.
Wrapping up
That’s how to install Next.js in an existing project without throwing away the work you already have. Install three packages, add the scripts, create the app folder, and your existing code can move in at whatever pace suits you.
These steps are written for Next.js 16, and they work the same on Next.js 15. The only real difference is next lint, which still exists in 15 and is gone in 16.
Thanks for reading. If you hit an error I didn’t cover, drop it in the comments and I’ll take a look.