ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β π nextjs/app/guides/migrating/from-vite β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
This guide will help you migrate an existing Vite application to Next.js.
There are several reasons why you might want to switch from Vite to Next.js:
If you have built your application with the default Vite plugin for React, your application is a purely client-side application. Client-side only applications, also known as single-page applications (SPAs), often experience slow initial page loading time. This happens due to a couple of reasons:
The previous issue of slow loading times can be somewhat managed with code splitting. However, if you try to do code splitting manually, you'll often make performance worse. It's easy to inadvertently introduce network waterfalls when code-splitting manually. Next.js provides automatic code splitting built into its router.
A common cause of poor performance occurs when applications make sequential client-server requests to fetch data. One common pattern for data fetching in an SPA is to initially render a placeholder, and then fetch data after the component has mounted. Unfortunately, this means that a child component that fetches data can't start fetching until the parent component has finished loading its own data.
While fetching data on the client is supported with Next.js, it also gives you the option to shift data fetching to the server, which can eliminate client-server waterfalls.
With built-in support for streaming through React Suspense, you can be more intentional about which parts of your UI you want to load first and in what order without introducing network waterfalls.
This enables you to build pages that are faster to load and eliminate layout shifts.
Depending on your needs, Next.js allows you to choose your data fetching strategy on a page and component basis. You can decide to fetch at build time, at request time on the server, or on the client. For example, you can fetch data from your CMS and render your blog posts at build time, which can then be efficiently cached on a CDN.
Next.js Proxy allows you to run code on the server before a request is completed. This is especially useful to avoid having a flash of unauthenticated content when the user visits an authenticated-only page by redirecting the user to a login page. The proxy is also useful for experimentation and internationalization.
Images, fonts, and third-party scripts often have significant impact on an application's performance. Next.js comes with built-in components that automatically optimize those for you.
Our goal with this migration is to get a working Next.js application as quickly as possible, so that you can then adopt Next.js features incrementally. To begin with, we'll keep it as a purely client-side application (SPA) without migrating your existing router. This helps minimize the chances of encountering issues during the migration process and reduces merge conflicts.
The first thing you need to do is to install next as a dependency:
npm install next@latest
Create a next.config.mjs at the root of your project. This file will hold your
Next.js configuration options.
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './dist', // Changes the build output directory to `./dist/`.
}
export default nextConfig
Good to know: You can use either
.jsor.mjsfor your Next.js configuration file.
If you're using TypeScript, you need to update your tsconfig.json file with the following changes
to make it compatible with Next.js. If you're not using TypeScript, you can skip this step.
tsconfig.node.json./dist/types/**/*.ts and ./next-env.d.ts to the include array./node_modules to the exclude array{ "name": "next" } to the plugins array in compilerOptions: "plugins": [{ "name": "next" }]esModuleInterop to true: "esModuleInterop": truejsx to react-jsx: "jsx": "react-jsx"allowJs to true: "allowJs": trueforceConsistentCasingInFileNames to true: "forceConsistentCasingInFileNames": trueincremental to true: "incremental": trueHere's an example of a working tsconfig.json with those changes:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"plugins": [{ "name": "next" }]
},
"include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
"exclude": ["./node_modules"]
}
You can find more information about configuring TypeScript on the Next.js docs.
A Next.js App Router application must include a
root layout
file, which is a React Server Component
that will wrap all pages in your application. This file is defined at the top level of the app
directory.
The closest equivalent to the root layout file in a Vite application is the
index.html file, which contains your
<html>, <head>, and <body> tags.
In this step, you'll convert your index.html file into a root layout file:
app directory in your src folder.layout.tsx file inside that app directory:export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
export default function RootLayout({ children }) {
return '...'
}
Good to know:
.js,.jsx, or.tsxextensions can be used for Layout files.
index.html file into the previously created <RootLayout> component while
replacing the body.div#root and body.script tags with <div id="root">{children}</div>:export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
<head>:export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
favicon.ico, icon.png, robots.txt are automatically added to the application
<head> tag as long as you have them placed into the top level of the app directory. After
moving
all supported files
into the app directory you can safely delete their <link> tags:export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
<head> tags with the
Metadata API. Move your final metadata
info into an exported
metadata object:import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My App',
description: 'My App is a...',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export const metadata = {
title: 'My App',
description: 'My App is a...',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
With the above changes, you shifted from declaring everything in your index.html to using Next.js'
convention-based approach built into the framework
(Metadata API). This approach enables you
to more easily improve your SEO and web shareability of your pages.
On Next.js you declare an entrypoint for your application by creating a page.tsx file. The
closest equivalent of this file on Vite is your main.tsx file. In this step, youβll set up the
entrypoint of your application.
[[...slug]] directory in your app directory.Since in this guide we're aiming first to set up our Next.js as an SPA (Single Page Application), you need your page entrypoint to catch all possible routes of your application. For that, create a new [[...slug]] directory in your app directory.
This directory is what is called an optional catch-all route segment. Next.js uses a file-system based router where folders are used to define routes. This special directory will make sure that all routes of your application will be directed to its containing page.tsx file.
page.tsx file inside the app/[[...slug]] directory with the following content:import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
Good to know:
.js,.jsx, or.tsxextensions can be used for Page files.
This file is a Server Component. When you run next build, the file is prerendered into a static asset. It does not require any dynamic code.
This file imports our global CSS and tells generateStaticParams we are only going to generate one route, the index route at /.
Now, let's move the rest of our Vite application which will run client-only.
'use client'
import React from 'react'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'
import React from 'react'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
This file is a Client Component, defined by the 'use client'
directive. Client Components are still prerendered to HTML on the server before being sent to the client.
Since we want a client-only application to start, we can configure Next.js to disable prerendering from the App component down.
const App = dynamic(() => import('../../App'), { ssr: false })
Now, update your entrypoint page to use the new component:
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
Next.js handles static image imports slightly different from Vite. With Vite, importing an image file will return its public URL as a string:
import image from './img.png' // `image` will be '/assets/img.2d8efhg.png' in production
export default function App() {
return <img src={image} />
}
With Next.js, static image imports return an object. The object can then be used directly with the
Next.js <Image> component, or you can use the object's
src property with your existing <img> tag.
The <Image> component has the added benefits of
automatic image optimization. The <Image>
component automatically sets the width and height attributes of the resulting <img> based on
the image's dimensions. This prevents layout shifts when the image loads. However, this can cause
issues if your app contains images with only one of their dimensions being styled without the other
styled to auto. When not styled to auto, the dimension will default to the <img> dimension
attribute's value, which can cause the image to appear distorted.
Keeping the <img> tag will reduce the amount of changes in your application and prevent the above
issues. You can then optionally later migrate to the <Image> component to take advantage of optimizing images by configuring a loader, or moving to the default Next.js server which has automatic image optimization.
/public into relative imports:// Before
import logo from '/logo.png'
// After
import logo from '../public/logo.png'
src property instead of the whole image object to your <img> tag:// Before
<img src={logo} />
// After
<img src={logo.src} />
Alternatively, you can reference the public URL for the image asset based on the filename. For example, public/logo.png will serve the image at /logo.png for your application, which would be the src value.
Warning: If you're using TypeScript, you might encounter type errors when accessing the
srcproperty. You can safely ignore those for now. They will be fixed by the end of this guide.
Next.js has support for .env
environment variables
similar to Vite. The main difference is the prefix used to expose environment variables on the
client-side.
VITE_ prefix to NEXT_PUBLIC_.Vite exposes a few built-in environment variables on the special import.meta.env object which
arenβt supported by Next.js. You need to update their usage as follows:
import.meta.env.MODE β process.env.NODE_ENVimport.meta.env.PROD β process.env.NODE_ENV === 'production'import.meta.env.DEV β process.env.NODE_ENV !== 'production'import.meta.env.SSR β typeof window !== 'undefined'Next.js also doesn't provide a built-in BASE_URL environment variable. However, you can still
configure one, if you need it:
.env file:# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
basePath to process.env.NEXT_PUBLIC_BASE_PATH in your next.config.mjs file:/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './dist', // Changes the build output directory to `./dist/`.
basePath: process.env.NEXT_PUBLIC_BASE_PATH, // Sets the base path to `/some-base-path`.
}
export default nextConfig
import.meta.env.BASE_URL usages to process.env.NEXT_PUBLIC_BASE_PATHpackage.jsonYou should now be able to run your application to test if you successfully migrated to Next.js. But
before that, you need to update your scripts in your package.json with Next.js related commands,
and add .next and next-env.d.ts to your .gitignore:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
# ...
.next
next-env.d.ts
dist
Now run npm run dev, and open http://localhost:3000. You should see your application now running on Next.js.
Example: Check out this pull request for a working example of a Vite application migrated to Next.js.
You can now clean up your codebase from Vite related artifacts:
main.tsxindex.htmlvite-env.d.tstsconfig.node.jsonvite.config.tsIf everything went according to plan, you now have a functioning Next.js application running as a single-page application. However, you aren't yet taking advantage of most of Next.js' benefits, but you can now start making incremental changes to reap all the benefits. Here's what you might want to do next:
<Image> componentnext/font<Script> componentβ β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ