Next.js Discord

Discord Forum

How to set different background colors for routes in Next.js App Router (without layout groups)?

Answered
American Wirehair posted this in #help-forum
Open in Discord
American WirehairOP
I'm building a Next.js app using the App Router, and I want to set different background colors for different routes. However, I want to do this in a clean and efficient way, and I have a few specific requirements:

# Requirements:
- Use a single shared layout.tsx file (i.e., no layout groups).
- Background color should be set on the <body> element.
- It should be applied server-side (i.e., no client-side flickering or flashes).
- The pages should still be statically generated (so no use of cookies or headers).
- Bonus: I'd like to use Tailwind CSS classes if possible.

# What I've considered so far:
## ❌ Option 1: Use layout groups

This works, and is probably the "correct" way. But we cant have a single shared root layout. This means that for each page transation we have to fetch components like footer and header again, even though 99% of the layout is the same, except for the body background color. This makes page transitions slower.

## ❌ Option 2: Set body color client-side using useEffect and pathname

This allows me to keep a single root layout.tsx, but since the background color is set on the client, it causes a visible color change flash when navigating or refreshing. I'd prefer the color to be rendered server-side.

## ❌ Option 3: Have each page.tsx import a route-specific .css file that sets the body color

In theory this should work, but in practice it causes issues. Likely due to CSS specificity problems during route transitions — previous styles linger, and the body doesn't consistently get the correct background color.

# Question:
How can I set route-specific <body> background colors in a way that satisfies the above constraints?
Is there a way to achieve this using just a shared layout and static rendering?
Answered by joulev
// app/body.tsx
'use client';

export function Body({ children }) {
  const pathname = usePathname();
  const colour = getColourForPath(pathname);

  return (
    <body style={{ background: colour }}>
      {children}
    </body>
  );
}

// app/layout.tsx
export default function Layout({ children }) {
  return (
    <html>
      <head>...</head>
      <Body>{children}</Body>
    </html>
  );
}
View full answer

5 Replies

@American Wirehair I'm building a Next.js app using the App Router, and I want to set different background colors for different routes. However, I want to do this in a clean and efficient way, and I have a few specific requirements: # Requirements: - Use a single shared layout.tsx file (i.e., no layout groups). - Background color should be set on the <body> element. - It should be applied server-side (i.e., no client-side flickering or flashes). - The pages should still be statically generated (so no use of cookies or headers). - Bonus: I'd like to use Tailwind CSS classes if possible. # What I've considered so far: ## ❌ Option 1: Use layout groups This works, and is probably the "correct" way. But we cant have a single shared root layout. This means that for each page transation we have to fetch components like footer and header again, even though 99% of the layout is the same, except for the body background color. This makes page transitions slower. ## ❌ Option 2: Set body color client-side using useEffect and pathname This allows me to keep a single root layout.tsx, but since the background color is set on the client, it causes a visible color change flash when navigating or refreshing. I'd prefer the color to be rendered server-side. ## ❌ Option 3: Have each page.tsx import a route-specific .css file that sets the body color In theory this should work, but in practice it causes issues. Likely due to CSS specificity problems during route transitions — previous styles linger, and the body doesn't consistently get the correct background color. # Question: How can I set route-specific <body> background colors in a way that satisfies the above constraints? Is there a way to achieve this using just a shared layout and static rendering?
// app/body.tsx
'use client';

export function Body({ children }) {
  const pathname = usePathname();
  const colour = getColourForPath(pathname);

  return (
    <body style={{ background: colour }}>
      {children}
    </body>
  );
}

// app/layout.tsx
export default function Layout({ children }) {
  return (
    <html>
      <head>...</head>
      <Body>{children}</Body>
    </html>
  );
}
Answer
American WirehairOP
Unfortunately this solution will set the color first when javascript is loaded client side, so the page will first render with a default bg color and then apply this color. Resulting in a visible flash. One of the requirements is to apply the bg server side.
American WirehairOP
Ok, it seems to work fine now. 🙂 I had another useEffect present causing the issue. I still think it doesn't set the background server side (since we apply "use client"), but it applies the background on the first render, so it's present immediately.

Thank you! 🙂