src/

Holds app or pages one level down, separating application code from root config files.

Signature
src/app/     the App Router, moved one level down
src/pages/   the Pages Router, moved one level down

// Stays at the project root:
// package.json, next.config.js, tsconfig.json, .env.*, public/

Move app or pages into src and Next.js finds it there. Nothing else changes about routing: src/app/blog/[slug]/page.tsx still serves /blog/hello. The point is purely organizational — the root becomes config and the source tree becomes code.

What has to stay at the root

  • public/ — it does not move.
  • package.json, next.config.js, tsconfig.json.
  • .env.* files.

What moves with it

Root-level convention files that sit beside app follow it in: instrumentation.ts, instrumentation-client.ts, mdx-components.tsx, and your proxy file all belong inside src once you adopt it. So do ordinary folders such as components and lib.

What trips people up

Adopt it and a few things silently stop matching. The paths object in tsconfig.json needs src/ added, or @/* imports resolve nowhere. Tailwind's content globs need the /src prefix or every class is purged. And Turbopack or webpack config paths that referenced ./app now point at nothing.

Example

Example · typescript
// my-app/
// ├── package.json          stays at the root
// ├── next.config.ts        stays at the root
// ├── tsconfig.json         stays at the root
// ├── .env.local            stays at the root
// ├── public/               stays at the root
// └── src/
//     ├── app/
//     │   ├── layout.tsx
//     │   └── page.tsx      ->  /
//     ├── components/
//     ├── lib/
//     ├── instrumentation.ts
//     └── mdx-components.tsx

// src/app/page.tsx — routing is unchanged by the move
export default function Page() {
  return <h1>Home</h1>
}

When to use it

  • Keep a root directory readable when config files start outnumbering source folders.
  • Match a monorepo convention where every package keeps code under src.
  • Group app, components and lib under one tree for tooling that scans a source root.
  • Separate application code from CI, Docker and infrastructure files at the root.

Frequently asked questions

Should the public folder go inside src in Next.js?

No. The public directory should remain in the root of your project, along with package.json, next.config.js, tsconfig.json and your .env.* files. Only application code moves into src.

Why is Next.js ignoring my src/app directory?

Because a root-level app or pages directory still exists — src/app and src/pages are ignored when either is present at the root. Delete or rename the root folder to complete the move.

Does using src change my URLs in Next.js?

No. Routing is identical: src/app/blog/[slug]/page.tsx still serves /blog/hello. The src segment is not part of any URL.

More examples

Updating tsconfig paths

Without the src/ prefix in paths, every @/ import breaks at once with 'Cannot find module', which at least fails loudly. The subtler case is a project that never had an alias: relative imports keep working after the move, so the missing paths entry goes unnoticed until someone adds the first @/ import.

Example · typescript
// tsconfig.json — stays at the project root
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

// Now resolves to src/components/button.tsx
import { Button } from '@/components/button'

Tailwind content globs after the move

This one fails without any error message. The build succeeds, the CSS file is produced, and every utility class has been purged because no file matched the globs — so the app renders completely unstyled. Whenever a src/ migration produces an unstyled page, check the content array first.

Example · typescript
// tailwind.config.ts
import type { Config } from 'tailwindcss'

export default {
  content: [
    // Before: './app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
} satisfies Config

Root files that must not move

The split is the confusing part: convention files that used to sit beside app follow it into src, while public/ and every config file stay behind. Moving public/ into src/ produces 404s for every asset, and moving .env.local means environment variables are simply undefined at build time with no warning.

Example · typescript
// Correct after adopting src/
// my-app/
// ├── next.config.ts        root
// ├── .env.local            root
// ├── public/logo.png       root  ->  served at /logo.png
// └── src/
//     ├── app/
//     ├── proxy.ts          inside src
//     └── instrumentation.ts inside src

// src/app/page.tsx
import Image from 'next/image'

export default function Page() {
  // Still /logo.png — public/ did not move, and never appears in the URL
  return <Image src="/logo.png" alt="Logo" width={200} height={40} />
}

Discussion

  • Be the first to comment on this lesson.