Next.js 13 Upgrade Guide

This guide will help you:

Upgrading

Node.js Version

The minimum Node.js version is now v16.8. See the Node.js documentation for more information.

Next.js Version

To update to Next.js version 13, run the following command using your preferred package manager:

npm install next@latest react@latest react-dom@latest eslint-config-next@latest
# or
yarn add next@latest react@latest react-dom@latest eslint-config-next@latest
# or
pnpm update next@latest react@latest react-dom@latest eslint-config-next@latest

ESLint Version

If you're using ESLint, you need to upgrade your ESLint version:

npm install -D eslint-config-next@latest

Note: You may need to restart the ESLint server in VS Code for the ESLint changes to take effect. Open the Command Palette (cmd+shift+p on Mac; ctrl+shift+p on Windows) and search for ESLint: Restart ESLint Server.

Next Steps

After you've updated, see the following sections for next steps:

Upgrading New Features

Next.js 13 introduced a new app directory with new features and conventions. However, upgrading to Next.js 13 does not require using the new app directory.

You can continue using pages with new features that work in both directories, such as the updated Image component, Link component, Script component, and Font optimization.

<Image/> Component

Next.js 12 introduced new improvements to the Image Component with a temporary import: next/future/image. These improvements included less client-side JavaScript, easier ways to extend and style images, better accessibility, and native browser lazy loading.

In version 13, this new behavior is now the default for next/image.

There are two codemods to help you migrate to the new Image Component:

  • next-image-to-legacy-image codemod: Safely and automatically renames next/image imports to next/legacy/image. Existing components will maintain the same behavior.
  • next-image-experimental codemod: Dangerously adds inline styles and removes unused props. This will change the behavior of existing components to match the new defaults. To use this codemod, you need to run the next-image-to-legacy-image codemod first.

The <Link> Component no longer requires manually adding an <a> tag as a child. This behavior was added as an experimental option in version 12.2 and is now the default. In Next.js 13, <Link> always renders <a> and allows you to forward props to the underlying tag.

For example:

import Link from 'next/link'

// Next.js 12: `<a>` has to be nested otherwise it's excluded
<Link href="/about">
  <a>About</a>
</Link>

// Next.js 13: `<Link>` always renders `<a>` under the hood
<Link href="/about">
  About
</Link>

To upgrade your links to Next.js 13, you can use the new-link codemod.

<Script> Component

The behavior of next/script has been updated to support both pages and app, but some changes need to be made to ensure a smooth migration:

  • Move any beforeInteractive scripts you previously included in _document.js to the root layout file (app/layout.tsx).
  • The experimental worker strategy does not yet work in app and scripts denoted with this strategy will either have to be removed or modified to use a different strategy (e.g. lazyOnload).
  • onLoad, onReady, and onError handlers will not work in Server Components so make sure to move them to a Client Component or remove them altogether.

Font Optimization

Previously, Next.js helped you optimize fonts by inlining font CSS. Version 13 introduces the new next/font module (beta) which gives you the ability to customize your font loading experience while still ensuring great performance and privacy. next/font is supported in both the pages and app directories.

While inlining CSS still works in pages, it does not work in app. You should use next/font instead.

See the Font Optimization page to learn how to use next/font.

Migrating from pages to app

Warning: The app directory is in beta and some conventions could change before stability is reached. We do not recommend using the app directory in production.

For many of you, this release will be the first time using React features that Next.js 13 builds on top of such as Server Components, Suspense, and promises inside components. When combined with new Next.js 13 features such as special files and layouts, migration means new concepts, mental models, and behavioral changes to learn.

We recommend reducing the combined complexity of these updates by breaking down your migration into smaller steps. The app directory is intentionally designed to work simultaneously with the pages directory to allow for incremental page-by-page migration.

  • The app directory supports nested routes and layouts. Learn more.
  • Use nested folders to define routes and a special page.js file to make a route segment publicly accessible. Learn more.
  • Special file conventions are used to create UI for each route segment. The most common special files are page.js and layout.js.
    • Use page.js to define UI unique to a route.
    • Use layout.js to define UI that is shared across multiple routes.
    • js, jsx or tsx file extensions can be used for special files.
  • You can colocate other files inside the app directory such as components, styles, tests, and more. Learn more.
  • Data fetching functions like getServerSideProps, getStaticProps, and getStaticPaths have been replaced with a new API inside app. Learn more.
  • pages/_app.js and pages/_document.js have been replaced with a single app/layout.js root layout. Learn more.
  • pages/_error.js has been replaced with more granular error.js special files. Learn more.
  • pages/404.js has been replaced with notFound()
  • pages/api/* currently remain inside the pages directory.

Step 1: Creating the app directory

Ensure you've updated to Next.js version 13, then opt into the new app directory in next.config.js:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
};

module.exports = nextConfig;

Then, create a new app directory at the root of your project (or src/ directory).

Step 2: Creating a Root Layout

Create a new app/layout.tsx file inside the app directory. This is a root layout that will apply to all routes inside app.

app/layout.tsx
export default function RootLayout({
  // Layouts must accept a children prop.
  // This will be populated with nested layouts or pages
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
  • The app directory must include a root layout.
  • The root layout must define <html>, and <body> tags since Next.js does not automatically create them
  • The root layout replaces the pages/_app.tsx and pages/_document.tsx files.
  • js, jsx, or tsx extensions can be used for layout files.

You should use the head.js special file to manage <head> HTML elements that change between route segments.

app/head.tsx
export default function Head() {
  return (
    <>
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </>
  )
}

Migrating _document.js and _app.js

If you have an existing _app or _document file, you can copy the contents (e.g. global styles) to the root layout (app/layout.tsx). Styles in app/layout.js will not apply to pages/*. You should keep _app/_document while migrating to prevent your pages/* routes from breaking. Once fully migrated, you can then safely delete them.

If you are using any React Context providers, they will need to be moved to a Client Component.

Migrating the getLayout() pattern to Layouts (Optional)

Next.js recommended adding a property to Page components to achieve per-page layouts in the pages directory. This pattern can be replaced with native support for nested layouts in the app directory.

See before and after example

Before

components/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div>
      <h2>My Dashboard</h2>
      {children}
    </div>
  );
}
pages/dashboard/index.js
import DashboardLayout from '../components/DashboardLayout';

export default function Page() {
  return <p>My Page</p>;
}

Page.getLayout = function getLayout(page) {
  return <DashboardLayout>{page}</DashboardLayout>;
};

After

  1. Remove the Page.getLayout property from pages/dashboard/index.js and follow the steps for migrating pages to the app directory.
app/dashboard/page.js
export default function Page() {
  return <p>My Page</p>;
}
  1. Move the contents of DashboardLayout into a new Client Component to retain pages directory behavior.
app/dashboard/DashboardLayout.js
'use client'; // this directive should be at top of the file, before any imports.

// This is a Client Component
export default function DashboardLayout({ children }) {
  return (
    <div>
      <h2>My Dashboard</h2>
      {children}
    </div>
  );
}
  1. Import the DashboardLayout into a new layout.js file inside the app directory.
app/dashboard/layout.js
import DashboardLayout from './DashboardLayout';

// This is a Server Component
export default function Layout({ children }) {
  return <DashboardLayout>{children}</DashboardLayout>;
}
  1. You can incrementally move non-interactive parts of DashboardLayout.js (Client Component) into layout.js (Server Component) to reduce the amount of component JavaScript you send to the client.

Step 3: Migrating next/head

In the pages directory, the next/head React component is used to manage <head> HTML elements such as title and meta . In the app directory, next/head is replaced with a new head.js special file. Learn more about head.js

See before and after example

Before

pages/index.tsx
import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My page title</title>
      </Head>
    </>
  )
}

After

app/head.tsx
export default function Head() {
  return (
    <>
      <title>My Page Title</title>
    </>
  )
}

Step 4: Migrating Pages

  • Pages in the app directory are Server Components by default. This is different from the pages directory where pages are Client Components.
  • Data fetching has changed in app. getServerSideProps, getStaticProps and getInitialProps have been replaced for a simpler API.
  • The app directory uses nested folders to define routes and a special page.js file to make a route segment publicly accessible.
  • pages Directoryapp DirectoryRoute
    index.jspage.js/
    about.jsabout/page.js/about
    blog/[slug].jsblog/[slug]/page.js/blog/post-1

We recommend breaking down the migration of a page into two main steps:

  • Step 1: Move the default exported Page Component into a new Client Component.
  • Step 2: Import the new Client Component into a new page.js file inside the app directory.

Note: This is the easiest migration path because it has the most comparable behavior to the pages directory.

Step 1: Create a new Client Component

  1. Create a new separate file inside the app directory (i.e. app/HomePage.tsx or similar) that exports a Client Component. To define Client Components, add the 'use client' directive to the top of the file (before any imports).
  2. Move the default exported page component from pages/index.js to app/HomePage.tsx.
app/HomePage.tsx
'use client';

// This is a Client Component. It receives data as props and
// has access to state and effects just like Page components
// in the `pages` directory.
export default function HomePage({ recentPosts }) {
  return (
    <div>
      {recentPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

Step 2: Create a new page

  1. Create a new app/page.tsx file inside the app directory. This is a Server Component by default.
  2. Import the HomePage.tsx Client Component into the page.
  3. If you were fetching data in pages/index.js, move the data fetching logic directly into the Server Component using the new data fetching APIs. See the data fetching upgrade guide for more details.
app/page.tsx
// Import your Client Component
import HomePage from './HomePage';

async function getPosts() {
  const res = await fetch('https://...');
  const posts = await res.json();
  return posts;
}

export default async function Page() {
  // Fetch data directly in a Server Component
  const recentPosts = await getPosts();
  // Forward fetched data to your Client Component
  return <HomePage recentPosts={recentPosts} />;
}
  1. If your previous page used useRouter, you'll need to update to the new routing hooks. Learn more.
  2. Start your development server and visit http://localhost:3000. You should see your existing index route, now served through the app directory.

Step 5: Migrating Routing Hooks

A new router has been added to support the new behavior in the app directory.

In app, you should use the three new hooks imported from next/navigation: useRouter(), usePathname(), and useSearchParams().

  • The new useRouter hook is imported from next/navigation and has different behavior to the useRouter hook in pages which is imported from next/router.
  • The new useRouter does not return the pathname string. Use the separate usePathname hook instead.
  • The new useRouter does not return the query object. Use the separate useSearchParams hook instead.
  • These new hooks are only supported in Client Components. They cannot be used in Server Components.
  • The useRouter hook imported from next/router is not supported in the app directory but can continue to be used in the pages directory.
app/page.tsx
'use client';

import { useRouter, usePathname, useSearchParams } from 'next/navigation';

export default function Page() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  // ...
}

In addition, the new useRouter hook has the following changes:

  • isPreview has been removed. Use the previewData function instead.
  • isFallback has been removed because fallback has been replaced.
  • The locale, locales, defaultLocales, domainLocales values have been removed because built-in i18n Next.js features are no longer necessary in the app directory. We will document a comprehensive example of how to achieve i18n using nested routing and generateStaticParams in the coming weeks.
  • basePath has been removed. The alternative will not be part of useRouter. It has not yet been implemented.
  • asPath has been removed because the concept of as has been removed from the new router.
  • isReady has been removed because it is no longer necessary. During static rendering, any component that uses the useSearchParams() hook will skip the prerendering step and instead be rendered on the client at runtime.

View the useRouter() API reference.

Step 6: Migrating Data Fetching Methods

The pages directory uses getServerSideProps, getStaticProps, and getStaticPaths to fetch data for pages. Inside the app directory, these previous data fetching APIs are replaced with a new API.

// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
fetch(URL, { cache: 'force-cache' });

// This request should be refetched on every request.
// Similar to `getServerSideProps`.
fetch(URL, { cache: 'no-store' });

// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
fetch(URL, { next: { revalidate: 10 } });

Server-side Rendering (getServerSideProps)

In the pages directory, getServerSideProps is used to fetch data on the server and forward props to the default exported React component in the file. The initial HTML for the page is prerendered from the server, followed by "hydrating" the page in the browser (making it interactive).

pages/dashboard.js
// `pages` directory

export async function getServerSideProps() {
  const res = await fetch(`https://.../data`);
  const data = await res.json();

  return { props: { data } };
}

export default function Dashboard({ data }) {
  return <div>{data}</div>;
}

In the app directory, we can colocate our data fetching inside our React components using Server Components. This allows us to send less JavaScript to the client, while maintaining the rendered HTML from the server.

By setting the cache option to no-store, we can indicate that the fetched data should never be cached. This is similar to getServerSideProps in the pages directory.

app/dashboard/page.tsx
// `app` directory

// This function can be named anything
async function fetchData() {
  const res = await fetch(`https://.../data`, { cache: 'no-store' });
  const data = await res.json();

  return data;
}

export default async function Dashboard() {
  const data = await fetchData();

  return <div>{data}</div>;
}

Accessing Request Object

In the pages directory, you can retrieve request-based data based on the Node.js HTTP API.

For example, you can retrieve the req object from getServerSideProps and use it to retrieve the request's cookies and headers.

pages/index.js
// `pages` directory

export async function getServerSideProps({ req, query }) {
  const authHeader = req.getHeaders()['authorization'];
  const theme = req.cookies['theme'];

  return { props: { ... }}
}

export default function Page(props) {
  return ...
}

The app directory exposes new read-only functions to retrieve request data:

app/page.tsx
// Next.js 13
import { cookies, headers } from 'next/headers';

async function getData() {
  const authHeader = headers().get('authorization');

  return ...;
}

export default async function Page() {
  // You can use `cookies()` or `headers()` inside Server Components
  // directly or in your data fetching function
  const theme = cookies().get('theme');
  const data = await getData();
  return ...;
}

Static Site Generation (getStaticProps)

In the pages directory, the getStaticProps function is used to pre-render a page at build time. This function can be used to fetch data from an external API or directly from a database, and pass this data down to the entire page as it's being generated during the build.

pages/index.js
// `pages` directory

export async function getStaticProps() {
  const res = await fetch(`https://.../data`);
  const data = await res.json();

  return { props: { data } };
}

export default function Index({ data }) {
  return <div>{data}</div>;
}

In the app directory, data fetching with fetch() will default to cache: 'force-cache', which will cache the request data until manually invalidated. This is similar to getStaticProps in the pages directory.

app/page.js
// `app` directory

// This function can be named anything
async function fetchData() {
  const res = await fetch(`https://.../data`);
  const data = await res.json();

  return data;
}

export default async function Index() {
  const data = await fetchData();

  return <div>{data}</div>;
}

Dynamic paths (getStaticPaths)

In the pages directory, the getStaticPaths function is used to define the dynamic paths that should be pre-rendered at build time.

pages/posts/[id].js
// `pages` directory

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }]
  };
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://.../posts/${params.id}`);
  const data = await res.json();

  return { props: { post: data.post } };
}

export default function Post({ post }) {
  return <PostLayout post={post}>
}

In the app directory, getStaticPaths is replaced with generateStaticParams.

generateStaticParams behaves similarly to getStaticPaths, but has a simplified API for returning route parameters and can be used inside layouts. The return shape of generateStaticParams is an array of segments instead of an array of nested param objects or a string of resolved paths.

app/posts/[id]/page.js
// `app` directory

export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }]
}

async function fetchPost(params) {
  const res = await fetch(`https://.../posts/${params.id}`);
  const data = await res.json();

  return data.post;
}

export default async function Post({ params }) {
  const post = await fetchPost(params);

  return <PostLayout post={post}>
}

Using the name generateStaticParams is more appropriate than getStaticPaths for the new model in the app directory. The get prefix is replaced with a more descriptive generate, which sits better alone now that getStaticProps and getServerSideProps are no longer necessary. The Paths suffix is replaced by Params, which is more appropriate for nested routing with multiple dynamic segments.


Replacing fallback

In the pages directory, the fallback property returned from getStaticPaths is used to define the behavior of a page that isn't pre-rendered at build time. This property can be set to true to show a fallback page while the page is being generated, false to show a 404 page, or blocking to generate the page at request time.

pages/posts/[id].js
// `pages` directory

export async function getStaticPaths() {
  return {
    paths: [...],
    fallback: 'blocking'
  };
}

export async function getStaticProps({ params }) {
  ...
}

export default function Post({ post }) {
  return ...
}

In the app directory the config.dynamicParams property controls how params outside of generateStaticParams are handled:

  • true: (default) Dynamic segments not included in generateStaticParams are generated on demand.
  • false: Dynamic segments not included in generateStaticParams will return a 404.

This replaces the fallback: true | false | 'blocking' option of getStaticPaths in the pages directory. The fallback: 'blocking' option is not included in dynamicParams because the difference between true 'blocking' and true is negligible with streaming.

app/posts/[id]/page.js
// `app` directory

export const dynamicParams = true;

export async function generateStaticParams() {
  return [...]
}

async function fetchPost(params) {
  ...
}

export default async function Post({ params }) {
  const post = await fetchPost(params);

  return ...
}

With dynamicParams set to true (the default), when a route segment is requested that hasn't been generated, it will be server-rendered and cached as static data on success.

Incremental Static Regeneration (getStaticProps with revalidate)

In the pages directory, the getStaticProps function allows you to add a revalidate field to automatically regenerate a page after a certain amount of time. This is called Incremental Static Regeneration (ISR) and helps you update static content without redeploying.

pages/index.js
// `pages` directory

export async function getStaticProps() {
  const res = await fetch(`https://.../posts`);
  const data = await res.json();

  return {
    props: { posts: data.posts },
    revalidate: 60,
  };
}

export default function Index({ posts }) {
  return (
    <Layout>
      <PostList posts={posts} />
    </Layout>
  );
}

In the app directory, data fetching with fetch() can use revalidate, which will cache the request for the specified amount of seconds.

app/page.js
// `app` directory

async function fetchPosts() {
  const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } });
  const data = await res.json();

  return data.posts;
}

export default async function PostList() {
  const posts = await fetchPosts();

  return <div>{posts.map((item) => ...)}</div>;
};

API Routes

API routes continue to work as expected without any changes. They should still be defined in the pages/api/* directory and not moved to the app directory. You can learn more about API routes in the stable Next.js docs.

It is worth noting that some use cases where an API route was used to keep access tokens secure when calling an external API from the client can now be done directly in Server Components. Learn more about data fetching.

Step 7: Styling

In the pages directory, global stylesheets are restricted to only pages/_app.js. With the app directory, this restriction has been lifted. Global styles can be added to any layout, page, or component.

Tailwind CSS

If you're using Tailwind CSS, you'll need to add the app directory to your tailwind.config.js file:

tailwind.config.js
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx}", // <-- Add this line
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
}

You'll also need to import your global styles in your app/layout.js file:

app/layout.js
import "../styles/globals.css";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Learn more about styling with Tailwind CSS


Codemods

Next.js provides Codemod transformations to help upgrade your codebase when a feature is deprecated. See Codemods for more information.