withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
45.51k stars 2.39k forks source link

CSS styles appear when running dev server, but are missing from production build #9093

Closed markgomez closed 6 months ago

markgomez commented 10 months ago

Astro Info

Astro                    v3.5.3
Node                     v18.18.0
System                   Linux (x64)
Package Manager          unknown [sic; via StackBlitz]
Output                   hybrid
Adapter                  @astrojs/node
Integrations             @astrojs/react

If this issue only occurs in one browser, which browser is a problem?

All browsers

Describe the Bug

CSS styles (Sass) in React project (Node SSR) appears in dev only, i.e., the production build is unstyled.

When inspecting page elements via Chrome's developer tools after running astro dev:

<head>
  <style>
    <script src="/.localservice@runtime.420f2654.js"></script>
    astro-island,astro-slot,astro-static-slot{display:contents}
  </style>
  <script><!-- omitted --></script>

  <!-- OK -->
  <style type="text/css" data-vite-dev-id="/home/projects/astro-node-ssr-react-styles-issue/src/styles/main.module.scss">
    ._main_106ym_1 {
      font-family: sans-serif;
      font-size: 1.6rem;
      color: maroon;
    }
  </style>

</head>

But after running astro build && node ./dist/server/entry.mjs:

<head>
  <style>
    <script src="/.localservice@runtime.420f2654.js"></script>
    astro-island,astro-slot,astro-static-slot{display:contents}
  </style>
  <script><!-- omitted --></script>

  <!-- missing post-processed styles from main.module.scss -->

</head>

To reproduce the issue:

  1. Wait for npm install && npm run dev to complete.
  2. Notice the page is styled.
  3. Click the Terminal pane and stop the dev server (Ctrl + C).
  4. Create a production build and start the app: npm run build && npm start
  5. Notice the page is now unstyled.

What's the expected result?

Production build should be styled.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/astro-node-ssr-react-styles-issue

Participation

bluwy commented 10 months ago

Looks like with build.inlineStylesheet: 'none', the CSS will exist in dist/client, however the node server will still not serve the CSS file, seems like something is off here.

lilnasy commented 10 months ago

This must have been a tricky one to pinpoint, thanks!

Makes sense why the CSS file gets deleted, nothing is referring to it. Must be a module graph bug. An adapter is not necessary for the bug to appear.

taktran commented 9 months ago

I'm getting a similar situation with an upgrade to Astro 3, however it seems to happen to all of my custom markdoc tags and custom heading ie, on dev server, styles get applied, but on build, the styles are missing. Other page styles seem ok. Worked on Astro 2.x too.

Astro                    v3.6.3
Node                     v18.16.0
System                   MacOS
Package Manager          yarn
Output                   'static'
Adapter                  None
Integrations             @astrojs/react, @astrojs/markdoc, @astrojs/sitemap

I've also tried the following, which doesn't change anything:

I'm trying to untangle my current project and create a stackblitz reproduction of this...

taktran commented 9 months ago

@lilnasy

Must be a module graph bug

Do you know where the module graph code is? I'm trying to debug the internals of astro, but not sure where to look. I have a working example using Astro 2.8.0, and a broken example using Astro 3.6.4, and I'm trying to see what the difference is.

(It does work on a clean build using Astro 3, so it could very well be something in our code or some edge case combination of integrations)

taktran commented 9 months ago

You can ignore my comments above, mine was another issue: https://github.com/withastro/astro/issues/9347#issuecomment-1849950846

markgomez commented 8 months ago

After playing around with this some more, it appears at least one .astro page needs to reference a stylesheet (can be any) in order for custom styles to be injected into <head>. Without that minimum reference (even when build.inlineStylesheet set to never bundles a .css file), custom styles will not be applied in the production build because <head> won't contain any references to them (only the astro-* CSS classes will be there).

As a workaround, I ended up using a wrapper <div> to create the minimum reference needed to trigger inclusion of all subsequent styles:

<div className={Styles.app}>
  <React.StrictMode>
    <App client:only="react" />
  </React.StrictMode>
</div>

Interestingly, the <style> tag containing the astro-* CSS classes are moved out of <head> and into the wrapper <div> while custom styles were injected into <head>.

Here's the full working example: https://stackblitz.com/edit/astro-node-ssr-react-styles-issue-workaround

alexturpin commented 7 months ago

I guess this is minor with Mark's workaround but breaking CSS modules in prod seems pretty important to me!

matthewp commented 6 months ago

The reproduction is fixed if you provide the full HTML structure:

---
export const prerender = false;

import Router from "../components/Router";
---

<html>
  <head>

  </head>
  <body>
    <Router client:only="react" />
  </body>
</html>

Is everyone else's issue caused by not having the html and head tag?

Boston343 commented 6 months ago

I believe I'm seeing the same issue. I'm using Keystatic and it allows you to create your own react components for custom MDX component previews. So also a react component where styles work in dev, but not in prod. I'm using hybrid rendering with @astrojs/netlify integration.

It's a simple component, but here's what it looks like:

// style import
import "@/styles/keystatic.css";

interface Props {
  variant: "tip" | "caution" | "danger" | "info";
}

const KeystaticAdmonition = ({
  variant,
  children,
}: Props & { children: React.ReactNode }) => {
  let color;
  if (variant === "tip") {
    color = "#6ee7b7";
  } else if (variant === "caution") {
    color = "#fcd34d";
  } else if (variant === "danger") {
    color = "#fca5a5";
  } else {
    color = "#7dd3fc";
  }

  return (
    <div className="ks-admonition" style={{ borderColor: color }}>
      <h5 className="ks-admonition__variant">{variant}</h5>
      <div>{children}</div>
    </div>
  );
};

export default KeystaticAdmonition;

And then here's the css file

.ks-admonition {
  border-radius: 0.375rem /* 6px */;
  border-left: 4px solid;
  padding-left: 1rem /* 16px */;
  padding-right: 1rem /* 16px */;
  padding-top: 0.75rem /* 12px */;
  padding-bottom: 0.75rem /* 12px */;
}

.ks-admonition__variant {
  font-weight: 700;
  text-transform: uppercase;
}
matthewp commented 6 months ago

Since there's no response to my question I'm going to assume this isn't a pressing issue. It is fixed by providing the full HTML structure. If anyone runs into a similar bug please feel free to file a new issue.

michrome commented 5 months ago

Hey @Boston343 did you solve this? I'm seeing the same with a setup like below. All's well when developing locally but the production build isn't including inline styles or referencing a stylesheet. I have "full HTML structure" but it's in the layout, not the page.

MDX content e.g. src/content/articles/my-article.mdx

---
layout: "../../layouts/article-layout-bulma.astro"
title: "My title"
---

My content.

Astro layout e.g. src/layouts/article-layout-bulma.astro

---
import "../styles/bulma.scss";
const { frontmatter } = Astro.props;
---

<html lang="en-GB">
  <head>
    <meta charset="utf-8" />
    <title>{frontmatter.title}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>

  <body class="bulma-columns bulma-container bulma-is-max-desktop">
    <article class="bulma-column bulma-content">
      <div class="">
        <h1>{frontmatter.title}</h1>
        <slot />
      </div>
    </article>
  </body>
</html>

Astro page e.g. src/pages/articles/[...slug].astro

---
import { getCollection } from "astro:content";
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
  const blogEntries = await getCollection("articles");
  return blogEntries.map((entry) => ({
    params: { slug: entry.slug },
    props: { entry },
  }));
}
// 2. For your template, you can get the entry directly from the prop
const { entry } = Astro.props;
const { Content } = await entry.render();
---

<Content />

SCSS Style e.g. src/styles/bulma.scss

@use "../node_modules/bulma/sass/utilities/initial-variables" with (
  $class-prefix: "bulma-"
);
@use "../node_modules/bulma/sass/themes";
@use "../node_modules/bulma/sass/base";
@use "../node_modules/bulma/sass/elements/content";
@use "../node_modules/bulma/sass/grid/columns";
@use "../node_modules/bulma/sass/layout";
michrome commented 5 months ago

For anyone coming to this, I removed the root cause of the error in my simplified examples above.

The root cause was I had a <style> tag in my Layout file. Astro doesn't seem to like that and generated output that was missing both a <html> tag and a <head> tag … so there wasn't a full HTML structure.

Bug-Reaper commented 3 months ago

Just throwing it out there, when following the CRA => Astro Docs it suggests:

---
// Import your root App component
import App from '../cra-project/App.jsx';
---
<!-- Use a client directive to load your app -->
<App client:load />

Which resulted in the weird behavior where CSS imported by the react-component worked in dev but got dropped in prod. Might be worth mentioning you may need something like <html><head></head><body><App client:load></body></html> to actually get the desired result.

This was somewhat tricky to debug and find the answer here. If it's easy to do, I'd suggest adding a warning if an astro page lacks the valid HTML that allows CSS to be added in prod?