gavmac00/nextjs-app-docs huggingface dataset:
Good to know: This is the easiest migration path because it has the most comparable behavior to the pages directory.
Step 1: Create a new Client Component
Create a new separate file inside the app directory (i.e. app/home-page.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).
Move the default exported page component from pages/index.js to app/home-page.tsx.
app/home-page.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 key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}
Step 2: Create a new page
Create a new app/page.tsx file inside the app directory. This is a Server Component by default.
Import the home-page.tsx Client Component into the page.
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 './home-page'
 
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()
// Fetch data directly in a Server Component
  const recentPosts = await getPosts()
  // Forward fetched data to your Client Component
  return <HomePage recentPosts={recentPosts} />
}
If your previous page used useRouter, you'll need to update to the new routing hooks. Learn more.
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 useRouter hook imported from next/router is not supported in the app directory but can continue to be used in the pages directory.
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.
You can use useSearchParams and usePathname together to listen to page changes. See the Router Events section for more details.
These new hooks are only supported in Client Components. They cannot be used in Server Components.
app/example-client-component.tsx 'use client'
 
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
 
export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()
 
  // ...
}
In addition, the new useRouter hook has the following changes:
// ...
}
In addition, the new useRouter hook has the following changes:
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. Learn more about i18n.
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
View the useRouter() API reference.
Step 6: Migrating Data Fetching Methods
The pages directory uses getServerSideProps and getStaticProps to fetch data for pages. Inside the app directory, these previous data fetching functions are replaced with a simpler API built on top of fetch() and async React Server Components.
app/page.tsx export default async function Page() {
  // This request should be cached until manually invalidated.
  // Similar to `getStaticProps`.
  // `force-cache` is the default and can be omitted.
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })
 
  // This request should be refetched on every request.
  // Similar to `getServerSideProps`.
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
  // Similar to `getStaticProps` with the `revalidate` option.
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })
 
  return <div>...</div>
}
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://...`)
  const projects = await res.json()
const projects = await res.json()
 
  return { props: { projects } }
}
 
export default function Dashboard({ projects }) {
  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}
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 getProjects() {
// This function can be named anything
async function getProjects() {
  const res = await fetch(`https://...`, { cache: 'no-store' })
  const projects = await res.json()
 
  return projects
}
 
export default async function Dashboard() {
  const projects = await getProjects()
 
  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}
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 }) {
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:
headers(): Based on the Web Headers API, and can be used inside Server Components to retrieve request headers.
cookies(): Based on the Web Cookies API, and can be used inside Server Components to retrieve cookies.
app/page.tsx // `app` directory
import { cookies, headers } from 'next/headers'
 
async function getData() {
  const authHeader = headers().get('authorization')
 
  return '...'
}
 
export default async function Page() {
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://...`)
  const projects = await res.json()
 
  return { props: { projects } }
}
return { props: { projects } }
}
 
export default function Index({ projects }) {
  return projects.map((project) => <div>{project.name}</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 getProjects() {
  const res = await fetch(`https://...`)
  const projects = await res.json()
 
  return projects
}
 
export default async function Index() {
  const projects = await getProjects()
 
  return projects.map((project) => <div>{project.name}</div>)
}
Dynamic paths (getStaticPaths)
}
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
import PostLayout from '@/components/post-layout'
 
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 post = await res.json()
 
  return { props: { post } }
}
 
export default function Post({ post }) {
  return <PostLayout 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
import PostLayout from '@/components/post-layout'
 
export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }]
}
 
async function getPost(params) {
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()
 
  return post
}
 
export default async function Post({ params }) {
return post
}
 
export default async function Post({ params }) {
  const post = await getPost(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 'blocking' and true is negligible with streaming.
app/posts/[id]/page.js // `app` directory
app/posts/[id]/page.js // `app` directory
 
export const dynamicParams = true;
 
export async function generateStaticParams() {
  return [...]
}
 
async function getPost(params) {
  ...
}
 
export default async function Post({ params }) {
  const post = await getPost(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
pages/index.js // `pages` directory
 
export async function getStaticProps() {
  const res = await fetch(`https://.../posts`)
  const posts = await res.json()
 
  return {
    props: { 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 getPosts() {
  const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
  const data = await res.json()
 
  return data.posts
}
const data = await res.json()
 
  return data.posts
}
 
export default async function PostList() {
  const posts = await getPosts()
 
  return posts.map((post) => <div>{post.name}</div>)
}
API Routes
API Routes continue to work in the pages/api directory without any changes. However, they have been replaced by Route Handlers in the app directory.
Route Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs.
app/api/route.ts export async function GET(request: Request) {}
Good to know: If you previously used API routes to call an external API from the client, you can now use Server Components instead to securely fetch data. Learn more about data fetching.
Step 7: Styling
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.
CSS Modules
Tailwind CSS
Global Styles
CSS-in-JS
External Stylesheets
Sass
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,mdx}', // <-- Add this line
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}
You'll also need to import your global styles in your app/layout.js file:
}
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.
Script Optimization
Layout Scripts
To load a third-party script for multiple routes, import next/script and include the script directly in your layout component:app/dashboard/layout.tsx import Script from 'next/script'
 
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <section>{children}</section>
      <Script src="https://example.com/script.js" />
    </>
  )
}The third-party script is fetched when the folder route (e.g. dashboard/page.js) or any nested route (e.g. dashboard/settings/page.js) is accessed by the user. Next.js will ensure the script will only load once, even if a user navigates between multiple routes in the same layout.
Application Scripts
Application Scripts
To load a third-party script for all routes, import next/script and include the script directly in your root layout:app/layout.tsx import Script from 'next/script'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
      <Script src="https://example.com/script.js" />
    </html>
  )
}
This script will load and execute when any route in your application is accessed. Next.js will ensure the script will only load once, even if a user navigates between multiple pages.
Recommendation: We recommend only including third-party scripts in specific pages or layouts in order to minimize any unnecessary impact to performance.
Strategy
Strategy
Although the default behavior of next/script allows you load third-party scripts in any page or layout, you can fine-tune its loading behavior by using the strategy property:
beforeInteractive: Load the script before any Next.js code and before any page hydration occurs.
afterInteractive: (default) Load the script early but after some hydration on the page occurs.
lazyOnload: Load the script later during browser idle time.
worker: (experimental) Load the script in a web worker.
Refer to the next/script API reference documentation to learn more about each strategy and their use cases.
Offloading Scripts To A Web Worker (Experimental)
Warning: The worker strategy is not yet stable and does not yet work with the app directory. Use with caution.
Scripts that use the worker strategy are offloaded and executed in a web worker with Partytown. This can improve the performance of your site by dedicating the main thread to the rest of your application code.
This strategy is still experimental and can only be used if the nextScriptWorkers flag is enabled in next.config.js:
next.config.js module.exports = {
  experimental: {
    nextScriptWorkers: true,
  },
}
Then, run next (normally npm run dev or yarn dev) and Next.js will guide you through the installation of the required packages to finish the setup:
Terminal npm run dev
You'll see instructions like these: Please install Partytown by running npm install @builder.io/partytown
Once setup is complete, defining strategy="worker" will automatically instantiate Partytown in your application and offload the script to a web worker.
pages/home.tsx import Script from 'next/script'
 
export default function Home() {
  return (
    <>
      <Script src="https://example.com/script.js" strategy="worker" />
    </>
  )
}
</>
  )
}
There are a number of trade-offs that need to be considered when loading a third-party script in a web worker. Please see Partytown's tradeoffs documentation for more information.
Inline Scripts
Inline scripts, or scripts not loaded from an external file, are also supported by the Script component. They can be written by placing the JavaScript within curly braces:
 <Script id="show-banner">
  {`document.getElementById('banner').classList.remove('hidden')`}
</Script>
Or by using the dangerouslySetInnerHTML property:
 <Script
  id="show-banner"
  dangerouslySetInnerHTML={{
    __html: `document.getElementById('banner').classList.remove('hidden')`,
  }}
/>
Warning: An id property must be assigned for inline scripts in order for Next.js to track and optimize the script.
Executing Additional Code
Executing Additional Code
Event handlers can be used with the Script component to execute additional code after a certain event occurs:
onLoad: Execute code after the script has finished loading.
onReady: Execute code after the script has finished loading and every time the component is mounted.
onError: Execute code if the script fails to load.
These handlers will only work when next/script is imported and used inside of a Client Component where "use client" is defined as the first line of code:app/page.tsx 'use client'
 
import Script from 'next/script'
 
export default function Page() {
  return (
    <>
      <Script
        src="https://example.com/script.js"
        onLoad={() => {
          console.log('Script has loaded')
        }}
      />
    </>
  )
}Refer to the next/script API reference to learn more about each event handler and view examples.
}Refer to the next/script API reference to learn more about each event handler and view examples.
Additional Attributes
There are many DOM attributes that can be assigned to a <script> element that are not used by the Script component, like nonce or custom data attributes. Including any additional attributes will automatically forward it to the final, optimized <script> element that is included in the HTML.
app/page.tsx import Script from 'next/script'
 
export default function Page() {
  return (
    <>
      <Script
        src="https://example.com/script.js"
        id="example-script"
        nonce="XUENAJFW"
        data-test="script"
      />
    </>
  )
}
Static Assets
Next.js can serve static files, like images, under a folder called public in the root directory. Files inside public can then be referenced by your code starting from the base URL (/).
For example, if you add me.png inside public, the following code will access the image:
Avatar.js import Image from 'next/image'
 
export function Avatar() {
  return <Image src="/me.png" alt="me" width="64" height="64" />
}
For static metadata files, such as robots.txt, favicon.ico, etc, you should use special metadata files inside the app folder.
Good to know:
The directory must be named public. The name cannot be changed and it's the only directory used to serve static assets.
Only assets that are in the public directory at build time will be served by Next.js. Files added at runtime won't be available. We recommend using a third-party service like AWS S3 for persistent file storage.
Lazy Loading
Lazy loading in Next.js helps improve the initial loading performance of an application by decreasing the amount of JavaScript needed to render a route.
It allows you to defer loading of Client Components and imported libraries, and only include them in the client bundle when they're needed. For example, you might want to defer loading a modal until a user clicks to open it.
There are two ways you can implement lazy loading in Next.js:
Using Dynamic Imports with next/dynamic
Using React.lazy() with Suspense
By default, Server Components are automatically code split, and you can use streaming to progressively send pieces of UI from the server to the client. Lazy loading applies to Client Components.
next/dynamic
next/dynamic is a composite of React.lazy() and Suspense. It behaves the same way in the app and pages directories to allow for incremental migration.
Examples
Importing Client Components
app/page.js 'use client'
Examples
Importing Client Components
app/page.js 'use client'
 
import { useState } from 'react'
import dynamic from 'next/dynamic'
 
// Client Components:
const ComponentA = dynamic(() => import('../components/A'))
const ComponentB = dynamic(() => import('../components/B'))
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
 
export default function ClientComponentExample() {
  const [showMore, setShowMore] = useState(false)
 
  return (
    <div>
      {/* Load immediately, but in a separate client bundle */}
      <ComponentA />
 
      {/* Load on demand, only when/if the condition is met */}
      {showMore && <ComponentB />}
      <button onClick={() => setShowMore(!showMore)}>Toggle</button>