Migrate to RunxBuild today!! Deploy More while Spending Less

Calculate your savings
unxBuild

Global Styles in Next.js: App Router and Pages Router Done Right

Sean

Platform Writer

Sep 28, 2026
9 min read

In the App Router you import globals.css exactly once, in app/layout.tsx. In the Pages Router you import it exactly once, in pages/_app.js. Import it anywhere else and Next.js stops the build.

Global Styles in Next.js: App Router and Pages Router Done Right

Every Next.js project has exactly one legal front door for global CSS, and the framework enforces it at build time rather than leaving it to convention. This is a tutorial for getting that one import right the first time, understanding why the restriction exists, and layering CSS Modules, Tailwind, CSS variables, and next/font on top of it without fighting the compiler.

Table of contents

App Router: import globals.css in app/layout.tsx

The App Router treats the root layout as the single entry point for anything that should apply to every route. Create app/globals.css and import it at the top of app/layout.tsx, above the component definition.

// app/layout.tsx
import './globals.css'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

That single import covers the entire route tree, including nested layouts and every page rendered under them. You do not import globals.css again in a nested layout.tsx or in an individual page.tsx — one import, at the root, is the whole contract.

A common early mistake is adding a second, more specific layout.tsx inside a route group and re-importing the same global stylesheet there out of habit. It is harmless at runtime since Next.js dedupes the CSS output, but it signals a misunderstanding worth fixing before someone reads it as meaningful.

Pages Router: only in pages/_app.js

The Pages Router has the same rule with a different address. Global CSS is only importable from pages/_app.js (or _app.tsx), and nowhere else in the pages tree.

// pages/_app.js
import '../styles/globals.css'

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />
}

_app.js wraps every page in the application, which is exactly why it is the only place Next.js allows a genuinely global stylesheet. If your project has both an app/ directory and a legacy pages/ directory during a migration, each router keeps its own global CSS entry point — they do not share one file automatically.

Why importing global CSS anywhere else throws a build error

This is not a linting suggestion. Import a global stylesheet from a regular component, and the Next.js build fails with an error naming the offending file and pointing back at _app.js or the root layout as the only valid location.

The reason is architectural, not arbitrary. Global CSS has no scoping boundary — a class name defined in it can affect any element on any page. If Next.js allowed importing it from arbitrary components, the same global rule could be pulled into the bundle multiple times through different code paths, and the framework would have no reliable way to guarantee the CSS is loaded exactly once, in a predictable order, before the page renders.

CSS Modules exist precisely to give you the opposite guarantee: styles scoped to the component that imports them, safe to import from anywhere, with generated class names that cannot collide. When you hit the global-CSS-not-allowed error, the fix is almost always to convert that file to a .module.css file rather than trying to relocate the import.

CSS Modules vs global CSS vs Tailwind’s globals

Three different mechanisms solve three different problems, and mixing them up is where most Next.js styling confusion comes from.

  • CSS Modules (Button.module.css) — scoped to one component, importable from anywhere, generates unique class names automatically
  • Global CSS (globals.css) — unscoped, one import location per router, right for resets, base element styles, and design tokens
  • Tailwind’s base/utilities layers — also global by nature, which is why Tailwind’s own directives live inside the same globals.css file, not a separate one
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --color-brand: #7f62f4;
  --radius-md: 0.5rem;
}

body {
  font-family: var(--font-sans);
}

A useful default: put resets, CSS variables, and Tailwind’s three directives in globals.css. Put everything specific to one component in a co-located .module.css file next to it. If you find yourself adding a new selector to globals.css for a single page, that is usually a sign it belongs in a module instead.

CSS variables for theming

CSS custom properties are the natural theming layer for a Next.js app because they are plain CSS — no build step, no JavaScript context provider required just to swap a color.

/* app/globals.css */
:root {
  --bg: #ffffff;
  --fg: #0a0a0a;
}

:root[data-theme='dark'] {
  --bg: #0a0a0f;
  --fg: #fafafa;
}

body {
  background: var(--bg);
  color: var(--fg);
}

Define the variables once in globals.css, toggle a data-theme attribute on <html> with a small client script or a cookie-read server component, and every component that reads var(--bg) updates without re-importing anything. Tailwind projects commonly map these same variables into tailwind.config.js so utility classes like bg-background resolve to the CSS variable rather than a hardcoded hex value.

next/font and where it fits

next/font is a separate mechanism from your global stylesheet, and mixing the two up causes layout-shift bugs. next/font downloads and self-hosts a font at build time, then hands you a class name or CSS variable to apply — it does not need a @font-face block in globals.css.

// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'], variable: '--font-sans' })

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.variable}>
      <body>{children}</body>
    </html>
  )
}

That --font-sans variable is now available globally because it is set on the <html> element, which means globals.css can reference var(--font-sans) in its body rule without ever importing a font file directly. This is the cleanest handoff between the two systems: next/font owns loading the font, globals.css owns applying it.

Ordering issues and production build differences

CSS import order matters more in production than it appears to in development. Next.js dev mode is forgiving about which stylesheet’s rules win when two have equal specificity, because Fast Refresh reinjects styles in whatever order they were last touched. A production build concatenates and orders CSS deterministically based on the import graph, which can surface specificity conflicts that never showed up locally.

The practical fix is to keep import order intentional: global resets and variables first, then Tailwind’s layers, then any third-party component library CSS, then your own module CSS last so component-specific rules can win ties. If a style looks right in next dev but wrong after next build, an import-order difference is the first thing to check, not a caching problem.

Run a production build locally before trusting a styling change is done — next build && next start catches the global-CSS-location error and most ordering surprises that next dev will happily ignore.

How this fits the rest of the stack

None of this is exotic, but it is exactly the kind of rule that costs an afternoon the first time you hit it, usually as a build failure with no obvious cause. Get the one global import right, lean on CSS Modules for everything else, and the rest of the styling story mostly takes care of itself. Once the app builds cleanly, the next question is usually where it runs — and the RunxBuild hosting calculator shows what a Next.js service actually costs against a real plan before you commit to one.

When the styles hold up in next build, the app can go out as a Next.js service from the RunxBuild dashboard straight from the repository.

Useful related references:

FAQ

Can I import global CSS in more than one file?

No. Next.js allows exactly one place per router: app/layout.tsx for the App Router, pages/_app.js for the Pages Router. Importing a global stylesheet from any other file fails the build with an explicit error naming the file and the allowed location.

Why does Next.js throw an error for global CSS outside _app or layout?

Global CSS has no scoping boundary, so Next.js cannot guarantee it loads once, in a predictable order, if it could be imported from arbitrary components. Restricting the import to one file gives the framework a single, deterministic point to bundle it from.

Where do I put Tailwind’s global styles?

In the same globals.css file that you import from app/layout.tsx or pages/_app.js. The three @tailwind directives are global by nature, so they belong alongside your resets and CSS variables, not in a separate module.

Does global CSS work the same way in both routers?

The mechanism is identical — one import location, applies to every route — but the location differs. App Router uses app/layout.tsx; Pages Router uses pages/_app.js. A project migrating between them needs both entry points during the transition if both trees are active.

Can I use CSS Modules and global CSS in the same project?

Yes, and most real projects do. Global CSS handles resets, variables, and Tailwind’s base layers from one entry point; CSS Modules handle everything scoped to a specific component, imported directly into that component’s file.

#global styles next js#Next.js#CSS Modules#Tailwind CSS#next/font