vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.56k stars 26.92k forks source link

CSS Modules sometimes don't load when using next/dynamic import #33286

Open jzxhuang opened 2 years ago

jzxhuang commented 2 years ago

Run next info (available from version 12.0.8 and up)

Operating System:
  Platform: darwin
  Arch: x64
  Version: Darwin Kernel Version 20.3.0: Thu Jan 21 00:06:51 PST 2021; root:xnu-7195.81.3~1/RELEASE_ARM64_T8101
Binaries:
  Node: 14.17.0
  npm: 6.14.13
  Yarn: 1.22.10
  pnpm: N/A
Relevant packages:
  next: 12.0.8
  react: 17.0.2
  react-dom: 17.0.2

What version of Next.js are you using?

12.0.8

What version of Node.js are you using?

14.17

What browser are you using?

Chrome

What operating system are you using?

macOS

How are you deploying your application?

Vercel

Describe the Bug

When loading a component using next/dynamic, CSS modules are sometimes not loaded correctly. This seems like it may be similar to https://github.com/vercel/next.js/issues/18252, which is described as fixed (although there is no linked PR showing the fix).

This bug only occurs when running next build + next start. Works as expected in development (next dev). Additionally, there seem to very specific scenarios where it occurs, which I've tried to outline as clearly and concisely as possible int he repro steps.

Expected Behavior

CSS modules should be loaded into the document so that your styles are applied.

To Reproduce

Repro Repo and Deployment

I've created a demo using create-next-app --typescript 12.0.8: https://github.com/jzxhuang/dynamic-import-css-module-next-bug

It's hosted at: https://dynamic-import-css-module-next-bug.vercel.app/

Isolated Reproduction Steps

  1. Go directly to https://dynamic-import-css-module-next-bug.vercel.app/broken/one, notice the button has a red background.
  2. Click the link to /foo. The button will now have a clear background, even though it is the exact same component with no changes whatsoever.

This can be reproduced with both SCSS and CSS modules. Repeating the above steps with https://dynamic-import-css-module-next-bug.vercel.app/broken/two will break the blue button. This is reproduced only in the "production" build, if you follow the same steps with next dev, the buttons on /foo should always be the correct color.

Cases where it works, but why?

The above steps are an isolated reproduction of this bug which we found in production. Now, here's where it starts getting super weird, we decided to do some more experimentation. I'm not sure if the following scenarios will be helpful or just throw you into a wild goose chase, but here we go:

  1. Go to https://dynamic-import-css-module-next-bug.vercel.app
  2. Click the link to /broken/one, DO NOT REFRESH
  3. Click the link to /foo.
  4. It's expected that the button is now red...

We also created a few other examples, under the routes /working/*. The only difference here is that we either load 2, 3, or 0 of the buttons. For some reason, these seems to work fine, but if you load only one button, as shown in broken/one and broken/two, the styling will be broken.

If you don't lazy load, everything is fine

Back to something that makes sense. The example /working/three shows that the blue button will always be the correct color. It is not dynamically imported in /foo, and thus does not have the same bug that the dynamically imported buttons do.

What's going on in the DOM?

On /broken/one, we see the button has the class comp-one_button__MtA0_ which is in the stylesheet 0ff09bacf4af1553.css. image

When we click into /foo, the button still has the same class, but the stylesheet is not being applied. image

I'm not sure what the root cause of this might be, but there's somehow a stylesheet missing when dynamically importing, and it seems to be related to using a component on the previous component inside the dynamically imported component.

jzxhuang commented 2 years ago

Hey, wondering if there are any insights or leads on this one! It's affecting us in production and we don't have any known workarounds — next/dynamic is required for to avoid a problematic library import that is not SSR-compatible. Thanks!

skumpulainen commented 2 years ago

Having similar issues with missing css files!

PlopTheReal commented 2 years ago

bump, same issue on nextjs 12.1.5

khujay15 commented 2 years ago

this looks similar to https://github.com/vercel/next.js/issues/17464 , which is about nextJs removing preloaded css.

there're some solutions to solve this issue, but kind of hacky way.

https://github.com/vercel/next.js/issues/17464#issuecomment-796430107

sjohnston88 commented 2 years ago

Also seeing this on NextJS 12.2.2. Can confirm that the issue goes away if not using dynamic imports.

sjohnston88 commented 2 years ago

The fix mentioned in #17464 has worked for me as a temporary workaround:

// In _app.tsx
const router = useRouter();

useEffect(() => {
  const handleRouteChange = () => {
    const styleElements = document.querySelectorAll('style[media="x"]');
    styleElements.forEach(styleTag => {
      styleTag.removeAttribute('media');
    });
  };

  router.events.on('routeChangeComplete', handleRouteChange);
  router.events.on('routeChangeStart', handleRouteChange);
}, [router]);
shaianest commented 1 year ago

the issue persist on next 12.2.4 and was not a thing for a long time and suddenly appeared.

hc0503 commented 1 year ago

This issue is happened in AppDir beta version of NextJS 13 as well.

simonxdev commented 1 year ago

Having the same Issue right now using NextJS 13. Any News here ?

Qavi-Nizamani commented 1 year ago

Having the same issue with Next.js 13, when navigating to any other page from the Home page, the CSS doesn't work.

mFirghi commented 1 year ago

Any lead on this so far, or any workarounds? It seems that it only happens on production after deployment for me.

simonxdev commented 1 year ago

In my Case, im building a Site with Data coming from a CMS. So i wanted to load the needed Components to the currently active Sites with Next/dynamic.

I tried all kind of stuff, but in the End solved the Issue with a switch. Guess we have to wait for a future release to get things fixed with dynamic. For now i would only recommend to use it with small components that dont load extra CSS.

Heres an Example how i did it, like this the Components will be returned the normal way and CSS Modules will be loaded:

          `{components.map((component: any, k: any) => {
              if (componentsProps[k].componentregion === regionNum) {

                switch (component) {
                  case "Example_component1":
                    return (
                      <Example_component1
                        componenttype={componentsProps[k].componenttype}
                        componentid={componentsProps[k].componentid}
                        key={k}
                      />
                    )
                  break;
                  case "Example_component2":
                    return (
                      <Example_component2
                        componenttype={componentsProps[k].componenttype}
                        componentid={componentsProps[k].componentid}
                        key={k}
                      />
                    )
                  break;
                  case "Example_component3":
                    return (
                      <Example_component3
                        componenttype={componentsProps[k].componenttype}
                        componentid={componentsProps[k].componentid}
                        key={k}
                      />
                    )
                  break;
                  case "Example_component4":
                    return (
                      <Example_component4
                        componenttype={componentsProps[k].componenttype}
                        componentid={componentsProps[k].componentid}
                        key={k}
                      />
                    )
                  break;
                }
              }
            })}`
stormsson commented 1 year ago

Same issue here :( v.13.4.3

danmondra commented 1 year ago

Same issue in 13.4.4

stormsson commented 1 year ago

opened repro scenario here https://github.com/vercel/next.js/issues/50300

Luderio commented 1 year ago

Same issue with next.js 13.4.7

BowlingX commented 1 year ago

A possible workaround for this problem is to move all async chunks into one group.

next.config.mjs:

export default {
  webpack(config, context) {
    const { isServer, dev } = context
    if(!isServer && !dev) {
       config.optimization.splitChunks.cacheGroups.asyncChunks = {
         enforce: true,
         type: "css/mini-extract",
         chunks: 'async',
       }
    }
   return config
  }
}
popmatik commented 1 year ago

Same issue here next.js v 13.4.19

r742davis commented 11 months ago

A possible workaround for this problem is to move all async chunks into one group.

next.config.mjs:

export default {
  webpack(config, context) {
    const { isServer, dev } = context
    if(!isServer && !dev) {
       config.optimization.splitChunks.cacheGroups.asyncChunks = {
         enforce: true,
         type: "css/mini-extract",
         chunks: 'async',
       }
    }
   return config
  }
}

Has anyone had luck with this solution? I have not tried it yet in production but it seems promising locally when removing !dev in the if statement

Krasnopir commented 11 months ago

A possible workaround for this problem is to move all async chunks into one group. next.config.mjs:

export default {
  webpack(config, context) {
    const { isServer, dev } = context
    if(!isServer && !dev) {
       config.optimization.splitChunks.cacheGroups.asyncChunks = {
         enforce: true,
         type: "css/mini-extract",
         chunks: 'async',
       }
    }
   return config
  }
}

Has anyone had luck with this solution? I have not tried it yet in production but it seems promising locally when removing !dev in the if statement

It does not work for me.


  const handleRouteChangeComplete = useCallback((page: string) => {
    ga.pageView(page);
    applyPreloadStylesheetLinks();
  }, []);

useEffect(() => {
    router.events.on('routeChangeComplete', handleRouteChangeComplete);

    return () => {
      router.events.off('routeChangeComplete', handleRouteChangeComplete);
    };
  }, [handleRouteChangeComplete, router.events]);

where

const applyPreloadStylesheetLinks = () => {
  const preloadStylesheetLinks = document.querySelectorAll<HTMLLinkElement>("link[rel='preload'][as='style']");
  preloadStylesheetLinks.forEach((preloadLink) => {

    const existingStylesheetLink = document.querySelector<HTMLLinkElement>(
      `link[rel='stylesheet'][href='${preloadLink.href}']`,
    );

    if (!existingStylesheetLink) {
      const stylesheetLink = document.createElement('link');
      stylesheetLink.rel = 'stylesheet';
      stylesheetLink.href = preloadLink.href;
      document.head.appendChild(stylesheetLink);
    }
  });
};

it works

alexgilbertDG commented 7 months ago

This is still an open issue for us !

stefanmkodo commented 5 months ago

This is still an issue in 14.2.1 !

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!

topolanekmartin commented 5 months ago

Please fix this bug

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!

juansaav commented 4 months ago

Still open issue

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!

walze commented 4 months ago

still happening on 14.2.4

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!

li4man0v commented 3 months ago

I’ve been looking into this issue for quite a while and found that my case is exactly like the one described in this GitHub issue. It’s a bit surprising (and a little frustrating) that CSS modules aren’t fully usable yet.

I came up with a workaround to fetch styles dynamically, and I thought I’d share it here in case it helps someone else:

import dynamic from 'next/dynamic';
import React from 'react';

const StyleProvider = dynamic(
  () =>
    import('styles/FancyStyles.module.css').then((module) => {
      interface ComponentProps {
        children: (style: typeof module.default) => JSX.Element;
      }
      const Component: React.FC<ComponentProps> = ({ children }) =>
        children(module.default);
      return Component;
    }),
  {
    loading: () => <p>Loading...</p>,
  }
);
const FancyComponent: React.FC = () => (
  <StyleProvider>
    {(style) => <SomeComponent className={style.fancy} />}
  </StyleProvider>
);

This solution works for now, but it definitely feels more like a stopgap than a permanent fix. If anyone has a better approach or any insights on how to handle this more effectively, I’d really appreciate your input!