How to set different background colors for routes in Next.js App Router (without layout groups)?
Answered
American Wirehair posted this in #help-forum
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?
# 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>
);
}
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 Wirehair 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.
nope. try it and see. it sets the colour 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! 🙂
Thank you! 🙂
@American Wirehair 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! 🙂
it doesn't set the background server sideit does set the bg server side. client components are prerendered on the server.