Open controversial opened 10 months ago
Still happening on next@14.1.0
.
Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!
We're also observing this behavior on Next 14.0.1
We are having the same problem between two route groups that import different reset.css files
I’ve updated the next.js version in the reproduction to 14.3.0-canary.9
and confirmed the issue still exists.
Hi everyone! Going through a lot of CSS-related issues as of recent—
To clarify, this is the expected behavior—if you navigate between Pages in App Router, both stylesheets will be active (I believe done for performance reasons). You will need to write CSS in a way that doesn't conflict, e.g., by using classNames that don't conflict. One such was is using CSS Modules.
I believe we need to update our documentation here to make that clearer.
Let me know if that makes sense! cc @controversial
I believe done for performance reasons—you don't want to continually re-request a Page when going back and forth
This explanation doesn’t make any sense to me—why would there be a performance implication? Removing and re-adding global stylesheets from the DOM that are imported on only one page wouldn't need to involve network requests.
I don't think it can be the “expected behavior” can be that page B is subject to global styles that page A imports (but page B does not)—this causes page B to display differently depending on whether or not the user has visited page A previously.
This behavior makes page components to behave in brittle, unpredictable ways, and is contrary to a reasonable developer’s expectation.
@samcx What leads you to think this is the expected behavior? And why would removing unloaded pages’ global stylesheets from the DOM require “re-requesting” anything?
@controversial You're right about the re-requesting not being a legitimate reason—let me ask the team again why such is the case!
Hi @controversial Next.js does not support global styles unless those styles are actually global (meaning apply to all pages and can live for the lifetime of the application). Earlier we actually enforced no global styles at all but added support for global styles because of the times that you legitimately want to use styles globally. However for page specific styles you need to use CSS Modules (or another styling technique).
One of the reasons we do not support global styles is the style ordering issue. Even with modules it can be tricky to ensure that stylehsheets always load in the correct order but this problem is compounded by global styles when any stylesheet can select any element within the document.
Another reason is that Next.js uses React's builtin support for stylesheets to integrate with Suspense. React does not remove stylesheets that it manages the moment you no longer render them because there are edge cases related to SSR and hydration where you might hydrate part of the page and then stop rendering a stylesheet and React cannot know whether the unhydrated parts of the page still depend on that stylesheet. At the moment React does not do any stylesheet garbage collection though we do intend in the future to add this. However the removal of unused stylesheets won't be guaranteed to happen during the commit that no longer renders a particular stylesheet. So when you navigate from page A to page B we won't necessarily immediately remove the stylesheets that were loaded with page A only.
One thing you do get for this however is coordination of UI reveals with when stylesheets are loaded so Next.js can start rendering the next page and ensure all the stylesheets are ready while either staying on the prior screen or showing an instant loading state for the next screen.
It can feel maybe limiting that global styles are not unconditionally supported but that's the current reality and we have no plans to change this in the future. Have you explored using CSS Modules to style your applciation?
Hi @gnoff, thanks for the explainer!
I do already style my application using CSS Modules, but here’s my use case for why adding and removing global CSS from a page/layout is important (even while using CSS modules):
background-color
or color-scheme
set on the body
element controls the color of the browser’s scroll bar, or the color of the browser chrome (in desktop Safari)rem
CSS unit can only be changed by setting a font-size
on the :root
elementbackground-color
and color
of the body
element, as well as some CSS variables that are applied globally, and the root font size.This would be trivially achievable with proper support for importing global CSS from layouts (the layout for each route group could just import a separate theme.scss
file)—except that in the current implementation, the global CSS files from the previous route group will never be removed.
I’m sure you can generalize this example to other cases where it would be useful to be able to have different global styles within different sections of an app.
I very much appreciate the deep dive on how & why React manages stylesheets, that context is genuinely super valuable for understanding the background behind this issue. I also now see the documentation of “caveats” on the documentation for React 19’s stylesheet support, which I hadn’t seen before.
It feels strange to me for the React reconciler to leave any elements in the DOM that are no longer part of the virtual DOM, but the edge case you mentioned
you might hydrate part of the page and then stop rendering a stylesheet and React cannot know whether the unhydrated parts of the page still depend on that stylesheet
is something I hadn’t considered.
You’re obviously much higher-context than I am on the constraints that exist within the React renderer / hydration process, but why couldn’t a mechanism exist like the following:
<style>
<head>
-hoisted stylesheet in the DOM, the set of all <style>
tag IDs that caused it to render. Each ID is added to the set at the point when React discovers + makes the decision decide to hoist that stylesheet, either on the client or the server. This could be as simple as a data-dependents
attribute written on the <head>
stylesheet, or it could be tracked using data structures in JS-land.<style>
tag is removed from a component in a hydrated part of the DOM, React removes its ID from the the set of dependents tracked for the stylesheet in the <head>
<head>
when its set of dependents becomes emptyThen, when React sees that a <style>
is removed from a component in the hydrated part of the DOM, React would know whether an unhydrated part of the page depends on that stylesheet based on whether its set of dependent IDs is non-empty.
This would be trivially achievable with proper support for importing global CSS from layouts (the layout for each route group could just import a separate theme.scss file)—except that in the current implementation, the global CSS files from the previous route group will never be removed.
@controversial This shouldn't be the case with multiple Route Groups since it is supposed to a Full Page Reload (Mentioned in the Good to know at the end of the section). Could be a 🐛—can you confirm this is the case on the latest canary?
I’m not necessarily talking about top-level route groups with separate root layouts, I’m talking about route groups generally (including those that aren’t at the top level of the app)
hi next, thx for your hard work on nextjs development, its super convenient as a full-stack framework. however, if you can make more convenient by following common sense would be better which each global css won't affect each other route group. just a kind suggestion.
I have (marketing) folder which import marketing.css, and the :root tailwind styling always appear on /admin layout which kind confused to me.
please consider scenario. thx
Link to the code that reproduces this issue
https://codesandbox.io/p/sandbox/loving-roman-scc0uh
To Reproduce
Current vs. Expected behavior
Currently, the global stylesheet added by page 2 is not removed once page 2 is unloaded.
I expect the global styles imported on page 2 to be removed when the user navigates back to the home page (which does not import those styles), and did not display those styles before page 2 was loaded.
Verify canary release
Provide environment information
Which area(s) are affected? (Select all that apply)
App Router, Routing (next/router, next/navigation, next/link)
Additional context
Reopening as a duplicate of #46817, which was closed as “please verify canary” despite a next@canary reproduction