In a Next.js app moving from the Pages Router to the App Router, what happens to pages/_app.tsx and pages/_document.tsx?
answer
- two files, one destination
- the root layout owns html and body
- Main becomes children
- NextScript has no manual equivalent
- providers need their own client wrapper
basics
~20 sBoth collapse into the root app/layout.tsx. That single file renders the <html> and <body> tags _document owned and holds the global CSS imports and shared providers _app owned. The app/ directory has no _app or _document equivalent.
solid answer
~40 s`_app.tsx` was the wrapper every page passed through — global CSS, context providers, and the `Component`/`pageProps` plumbing. `_document.tsx` was the server-only shell that rendered the document scaffolding using `Html`, `Head`, `Main` and `NextScript` from `next/document`. In the App Router both jobs land on the root `app/layout.tsx`: it is the only place required to render `<html>` and `<body>`, it is where global stylesheets get imported, and it wraps every route beneath it. Two consequences catch people out. Providers that rely on React context or hooks must live in a client component, so a common shape is a Server Component root layout rendering a `'use client'` `<Providers>` wrapper. And `<Head>` from `next/head` has no place here — head tags come from the metadata API instead, via an exported `metadata` object or `generateMetadata`.
code
tsx · 19 lines// app/layout.tsx — root layout replacing _app and _document
import './globals.css';
import { Providers } from './providers';
export const metadata = { title: 'Acme', description: 'Store' };
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}go deeper
Know that app/layout.tsx is the root layout and that it, not _app or _document, renders the <html> and <body> tags and imports the global stylesheet.
Explain what each old file was responsible for and where each responsibility lands, including that <Main /> becomes the children prop and that head tags now come from the metadata API.
Demonstrate the provider migration: a Server Component root layout wrapping a thin 'use client' provider component, and why marking the layout itself as a client component defeats the migration.
Own the sequencing risk — identify which _document responsibilities (CSS-in-JS style extraction especially) block routes from moving, and decide whether to swap the styling approach first or accept a longer coexistence window.
## What the two files did **`pages/_app.tsx`** was Next's custom App component. Every page render passed through it, receiving `{ Component, pageProps }` and returning `<Component {...pageProps} />` wrapped in whatever the application needed around it. It was the only place a global stylesheet could be imported, and it was where teams put React context providers, theme wrappers, analytics initialisation, and error boundaries. Because it persisted across client-side navigations, it was also the place any state that had to survive a route change lived. The per-page layout pattern — `Component.getLayout?.(page) ?? page` — was a convention layered on top of it, not a framework feature. **`pages/_document.tsx`** was a different animal: a server-only file rendered once per request, never on the client, that produced the surrounding HTML document. It imported `Html`, `Head`, `Main` and `NextScript` from `next/document` and had to render all four. `<Main />` was the slot the app rendered into; `<NextScript />` emitted the framework's script tags. Because it never ran in the browser, it could not carry event handlers or use most hooks, and it was the place for `lang` attributes, `<body>` classes, and server-side style extraction for CSS-in-JS libraries. ## Where each responsibility goes In `app/`, neither file exists. The root layout — `app/layout.tsx` — absorbs both: ```tsx // app/layout.tsx import './globals.css'; export const metadata = { title: 'Acme' }; export default function RootLayout({ children }) { return ( <html lang="en"> <body className="antialiased">{children}</body> </html> ); } ``` Mapping it out: - `<Html lang>` and `<body>` from `_document` → the literal `<html>` and `<body>` elements the root layout returns. The root layout is the one layout that is *required* to render them; nested layouts must not. - `<Main />` → the `children` prop. Whatever route is active renders in that slot. - `<NextScript />` → nothing. Next injects its own scripts; there is no manual equivalent, and trying to render one is a common porting mistake. - Global CSS imports from `_app` → an import in the root layout. The App Router also relaxes the old rule: global CSS may now be imported from any layout or page, not just one privileged file. - Context providers from `_app` → a client component rendered inside the root layout. - `<Head>` from `next/head` and the `<Head>` from `next/document` → the metadata API. You export a `metadata` object or a `generateMetadata` function and Next emits the tags. - `Component.getLayout` per-page layouts → nested `layout.tsx` files, which is the framework feature that convention was imitating. ## The provider problem This is the part migrations stumble on. The root layout is a Server Component. A typical `_app` was full of things that cannot be — a theme provider holding state, a query client, an auth context. Those all need `'use client'`, and marking the root layout itself as a client component would drag the entire application into the client bundle, throwing away the main reason to migrate. The standard shape is a thin client boundary: ```tsx // app/providers.tsx 'use client'; export function Providers({ children }) { return <ThemeProvider><QueryProvider>{children}</QueryProvider></ThemeProvider>; } ``` and the root layout renders `<Providers>{children}</Providers>` inside `<body>`. Because `children` is passed *through* the client boundary rather than imported by it, the pages underneath stay Server Components. ## Practical notes for a real migration A custom `_document` is often the hardest piece to port, because its usual reason for existing was server-side style extraction for a CSS-in-JS library. Those libraries need a client runtime and a manual insertion hook that no longer exists in the same form, so check the library's App Router support before scheduling the migration of a route that depends on it — this is frequently what pins a route in `pages/` longest. Also remember that during a partial migration both files keep working for everything still under `pages/`. You are not deleting them on day one; you are duplicating their responsibilities into the root layout and removing them only when the last page route is gone. That duplication is real cost — global CSS imported in two places, providers instantiated twice, analytics registered twice — and it is a reason to keep the half-migrated window short.
- Why not just put 'use client' at the top of the root layout so your old _app providers work unchanged?Because `'use client'` marks a boundary for the whole module graph beneath that import chain. Making the root layout a client component pushes the entire app toward the client bundle and gives up Server Component rendering everywhere, which is usually the point of migrating. The fix is a thin client `<Providers>` component that receives `children` as a prop, so pages passed through it stay on the server.
- _document rendered <NextScript /> explicitly. What is the App Router equivalent?There isn't one, and you should not look for it. Next injects its own script tags around the streamed output; the root layout only renders `<html>` and `<body>` plus your content. Importing anything from `next/document` inside `app/` is a porting error, not a compatibility path.
- Where do page title and meta description tags come from now that next/head is out?From the metadata API: a layout or page exports a `metadata` object, or a `generateMetadata` function when the values depend on the route's data, and Next renders the corresponding tags into the document head. You no longer render head elements as JSX from inside the component tree.
saying these in an interview costs you the question
- Says you create app/_app.tsx and app/_document.tsx
- Renders NextScript or imports next/document inside app/
- Puts 'use client' on the root layout to keep old providers
- Claims global CSS can still only be imported in one file
- Keeps using next/head for titles in App Router pages