gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.26k stars 10.31k forks source link

Gatsby serve | Styles are not loaded on initial site load #9121

Closed aamorozov closed 6 years ago

aamorozov commented 6 years ago

Description

Currently, when running gatsby build && gatsby serve and navigating to the page where the site is being served, some of the styles are not loading initially. However, if I navigate to any page within the app - it loads all the styles correctly. I am trying to isolate which exactly styles are not loaded, but there are no console/network errors at the moment.

I am using jsxstyle. There are no issues when running dev build.

Steps to reproduce

This is happening in a private organization project, but I can put together a repo to reproduce.

Expected result

All styles are loaded on initial page load.

Actual result

Some of the styles are not loaded initially.

Environment

System:
    OS: macOS High Sierra 10.13.6
    CPU: x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 10.12.0 - /usr/local/bin/node
    Yarn: 1.10.1 - /usr/local/bin/yarn
    npm: 6.4.1 - /usr/local/bin/npm
  Browsers:
    Chrome: 69.0.3497.100
    Firefox: 62.0.3
    Safari: 12.0
  npmPackages:
    gatsby: 2.0.18 => 2.0.18
    gatsby-plugin-flow: 1.0.2 => 1.0.2
    gatsby-plugin-jsxstyle: 0.0.3 => 0.0.3
    gatsby-plugin-react-helmet: 3.0.0 => 3.0.0
  npmGlobalPackages:
    gatsby-cli: 1.1.18
    gatsby: 1.9.277
DSchau commented 6 years ago

@aamorozov would you be able to put together a reproduction? It really helps us dive in much more quickly, and it's a big time saver for us.

Thank you!

aamorozov commented 6 years ago

Sure - I'll create a setup matching to my project as close as possible during the day @DSchau

DSchau commented 6 years ago

Thanks so much! I do feel like there are similar issues to this, so we may merge this issue with one of those once we learn more!

e.g. see #1836

appleJax commented 6 years ago

I have never used jsxstyle, but I had a similar issue with material-ui.

The problem for me was that in Gatsby, each page produces a separate html tree, so the material-ui class-name generator was assigning the same class name to different components on different pages.

Material-ui has an option to customize the generated class prefix, so my solution was to use a different prefix for each page. Not sure if jsxstyle has a similar configuration option.

AryanJ-NYC commented 6 years ago

Data point: Like @appleJax , I too have this issue when using material-ui.

Edit: @DSchau this can be reproduced here: https://github.com/AryanJ-NYC/webdev-coach-blog

appleJax commented 6 years ago

@AryanJ-NYC @aamorozov you can also create client-side routes with the @reach/router package. I think this is the recommended way? https://www.gatsbyjs.org/docs/building-apps-with-gatsby/

AryanJ-NYC commented 6 years ago

I ended up not using a withStyles material-ui method call and all is well.

appleJax commented 6 years ago

@AryanJ-NYC if you want to use withStyles on multiple pages, just pass a unique prefix as a prop to the Layout component and forward it to JssProvider. Looking at your repo, you'd modify the Layout component like this:

// Layout.js

const TemplateWrapper = ({ children, classes, classPrefix }) => {
  const generateClassName = createGenerateClassName({
    productionPrefix: classPrefix
  });

  return (
    <>
      <Helmet title="The WebDev Coach" />
      <JssProvider generateClassName={generateClassName}>
        // ...
      </JssProvider>
    </>
  );
});

And then instantiate Layout like so:

// about-page.js

const AboutPage = () => <Layout classPrefix='ap'> { /* ... */ } </Layout>
AryanJ-NYC commented 6 years ago

@appleJax 🙏 🙏 🙏

AryanJ-NYC commented 6 years ago

Update (in case anyone comes looking for help): I just ended up following the material-ui gatsby example: https://github.com/mui-org/material-ui/tree/master/examples/gatsby

aamorozov commented 6 years ago

@DSchau Sorry for the delay, here is a repo to reproduce https://github.com/aamorozov/gatsby-jsxstyle-issue. To replicate:

  1. Install packages
  2. Run yarn build && yarn serve
  3. Navigate to http://localhost:9000 - the images are in a column
  4. Go to page 2
  5. Go back to homepage - the images are in a row
ybv commented 6 years ago

Leaving here if anyone else stumbles up on the same issue while using Gatsby v2 + Material UI; I had to remove withRoot from all child components and have just one withRoot HOC per page as mentioned here

appleJax commented 6 years ago

@aamorozov I cloned your repo and tested it out. There is a line that you forgot to delete in pages/index.js (the Section component doesn't exist):

// src/pages/index.js

import Section from '../components/section';

After I deleted that line, I ran yarn build && yarn serve, navigated to localhost:9000, and everything looked normal, with the images in a row. I navigated back and forth from / to /page-2., and everything remained consistent.

Can you confirm your reproduction repo does not display the correct styles on the initial page load?

My environment is almost the same as yours:

OS: macOS High Sierra 10.13.6
Node: 10.12.0
Yarn: 1.10.1
npm: 6.4.1

Browsers Tested:
- Chrome: 70.0.3538.67
- Firefox: 63.0
- Safari: 11.1.2
aamorozov commented 6 years ago

@appleJax I just updated the repo and removed that component. The environment is indeed the same. I do notice now that the issue persists, but is not happening on every recompile. If I run yarn build the first time it might show me the correct layout, but I tried rebuilding a few times, with/without removing the old cache and it showed me the issue on 2nd rebuild...

appleJax commented 6 years ago

@aamorozov I think I've found a fix. Let me know if this works for you. I think gatsby-plugin-jsxstyle is using the wrong ssr API. Try making the following change to node_modules/gatsby-plugin-jsxstyle/gatsby-ssr.js:

// node_modules/gatsby-plugin-jsxstyle/gatsby-ssr.js

// Change this line
// exports.onRenderBody = ({ setHeadComponents }) => {

// To this
exports.replaceRenderer = ({ setHeadComponents }) => {
aamorozov commented 6 years ago

@appleJax

Unfortunately, it didn't work. The issue still persists randomly on the repro project and is constant on the actual project where the codebase is bigger. Thank you for the effort, i will keep looking - i think it's an issue either within the gatsby-plugin-jsxstyle or jsxstyle-webpack-plugin and not necessarily related to gatsby itself..

appleJax commented 6 years ago

@aamorozov I think you're right, jsxstyle-webpack-plugin seems to be non-deterministic with class-name generation. This will at least make the class names deterministic:

// node_modules/gatsby-plugin-jsxstyle/gatsby-node.js

// Change this line
// use: [JsxstylePlugin.loader],

// To this
  use: [{
    loader: JsxstylePlugin.loader,
    options: {
      classNameFormat: 'hash'
    }
  }]

It fixes the issue for your reproduction repo, but it's hard to tell with such a small codebase. Let me know if it works for your larger project.

meyer commented 6 years ago

Hi, maintainer of jsxstyle here. I am not familiar with the specifics of how Gatsby caches stuff between builds, but if the issue is that Gatsby doesn’t render things in a single webpack build then there will most definitely be issues with mismatched/missing styles. Each stylesheet is unique to each build. However, there is a (not very well documented) option to keep an on-disk cache of the groups of styles that jsxstyle has seen. This cache file ensures that groups of styles are keyed with the same index regardless of webpack’s module iteration order. This allows users to use the shorter CSS classname format while keeping builds deterministic.

Ideally this cache file would be located in the .cache folder. I managed to get it working with the following:

const JsxstylePlugin = require('jsxstyle-webpack-plugin');
const path = require('path');

exports.onCreateWebpackConfig = ({ store, actions }) => {
  const { program } = store.getState();

  actions.setWebpackConfig({
    plugins: [new JsxstylePlugin()],
    module: {
      rules: [
        {
          test: /\.(?:jsx?|tsx)$/,
          use: [
            {
              loader: JsxstylePlugin.loader,
              options: {
                cacheFile: path.resolve(
                  program.directory,
                  '.cache',
                  'jsxstyle-cache.txt'
                ),
              },
            },
          ],
        },
      ],
    },
  });
};

While I was digging around in the .cache folder I saw a subfolder called caches that already has a folder for gatsby-plugin-jsxstyle. Is that cache folder path accessible from onCreateWebpackConfig?

meyer commented 6 years ago

Ah neat, looks like Gatsby passes a Cache instance to each plugin function:

https://github.com/gatsbyjs/gatsby/blob/fa8ef4e/packages/gatsby/src/utils/api-runner-node.js#L103-L123

This seems to do the trick:

const JsxstylePlugin = require('jsxstyle-webpack-plugin');
const path = require('path');

exports.onCreateWebpackConfig = ({ actions, cache }) => {
  actions.setWebpackConfig({
    plugins: [new JsxstylePlugin()],
    module: {
      rules: [
        {
          test: /\.(?:jsx?|tsx)$/,
          use: [
            {
              loader: JsxstylePlugin.loader,
              options: {
                cacheFile: path.resolve(cache.directory, 'style-key-cache.txt'),
              },
            },
          ],
        },
      ],
    },
  });
};

Since this issue is an implementation bug (fixed in https://github.com/jsxstyle/jsxstyle/pull/123, thx @aamorozov) I’d say it can be closed here.

meyer commented 6 years ago

Bugfix PR has been merged and published. Thanks!

aamorozov commented 6 years ago

Closing the issue since it was addressed in jsxstyle project

DavidHe1127 commented 5 years ago

@appleJax I've followed your approach to have unique class prefix in each page by passing the prefix to Layout Component but I still have the class problem - about-us page has contact-us prefix.

Any idea?

appleJax commented 5 years ago

@DavidHe1127 I'd have to see your code to know what you're doing wrong. If you post a link to your repo, I can take a look. The class-prefix trick was just my little hacky solution. I think best practice would be to follow the official MUI gatsby example found here: https://github.com/mui-org/material-ui/tree/master/examples/gatsby

DavidHe1127 commented 5 years ago

@appleJax Thanks for your response. I actually overwrite createPages api provided by Gatsby to create pages using templates. Not sure if that matters. But anyway, I can show you my code snippets below.

layout.js

import React from 'react';
import PropTypes from 'prop-types';
import { StaticQuery, graphql } from 'gatsby';
import styled from 'styled-components';
import JssProvider from 'react-jss/lib/JssProvider';
import { createGenerateClassName } from '@material-ui/core/styles';

import PageHeader from 'components/common/PageHeader';
import PageFooter from 'components/common/PageFooter';
import './layout.css';

const ContentContainer = styled.div`
  min-height: 100vh;
  overflow: hidden;
  display: block;
  position: relative;
  padding-bottom: 80px;
`;

const Layout = ({ children, classPrefix }) => {
  const generateClassName = createGenerateClassName({
    productionPrefix: classPrefix,
  });

  return (
    <StaticQuery
      query={graphql`
        query SiteTitleQuery {
          site {
            siteMetadata {
              title
            }
          }
        }
      `}
      render={data => (
        <JssProvider generateClassName={generateClassName}>
          <ContentContainer>
            <PageHeader siteTitle={data.site.siteMetadata.title} />
            <div>{children}</div>
            <PageFooter />
          </ContentContainer>
        </JssProvider>
      )}
    />
  );
};

Layout.propTypes = {
  children: PropTypes.node.isRequired,
};

export default Layout;

templates/contact-us.js

import React from 'react';
import styled from 'styled-components';
import { withStyles } from '@material-ui/core/styles';
import { Link, graphql } from 'gatsby';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
import TextField from '@material-ui/core/TextField';

import Email from '@material-ui/icons/Email';
import Geolocation from '@material-ui/icons/Room';
import Phone from '@material-ui/icons/Phone';

import Form from 'components/contactUs/Form';
import Layout from 'components/layout';
import CompanyInfor from 'components/contactUs/CompanyInfor';
import Button from 'components/common/Button';
import SEO from 'components/seo';

const Header = styled.div`
  display: inline-block;
  width: 100%;
  height: 270px;
  background-color: #5d6d7e;
  padding: 48px;
  margin-bottom: 48px;
`;

const Title = styled.h2`
  margin-top: 20px;
  font-size: 44px;
  font-weight: bold;
  color: #fff;
  text-align: center;
`;

const styles = theme => ({
  input: {
    marginTop: 24,
    marginBottom: 24,
    marginLeft: 16,
    marginRight: 16,
    minWidth: 450,
    '&:focus': {
      borderColor: '#777',
    },
  },
  cssOutlinedInput: {
    '&$cssFocused $notchedOutline': {
      borderColor: '#777',
    },
  },
  cssLabel: {
    '&$cssFocused': {
      color: '#777',
    },
  },
  cssFocused: {},
  notchedOutline: {},
  formTitle: {
    marginLeft: 16,
  },
  icon: {
    fontSize: 32,
  },
});

const ContactUs = ({ data, classes }) => {
  const {
    markdownRemark: { frontmatter: content },
  } = data;

  const getInTouch = content.get_in_touch_with_us;

  const inputProps = {
    classes: {
      root: classes.cssOutlinedInput,
      focused: classes.cssFocused,
      notchedOutline: classes.notchedOutline,
    },
  };

  const inputLabelProps = {
    classes: {
      root: classes.cssLabel,
      focused: classes.cssFocused,
    },
  };

  return (
    <Layout classPrefix="contact-us">
      <SEO {...content.metadata} />
      <Header>
        <Title>{getInTouch.header}</Title>
      </Header>
      <TextField
        label="Name"
        className={classes.input}
        margin="normal"
        variant="outlined"
        InputLabelProps={inputLabelProps}
        InputProps={inputProps}
      />
    </Layout>
  );
};

export default withStyles(styles)(ContactUs);

export const ContactUsQuery = graphql`
  query($id: String!) {
    markdownRemark(id: { eq: $id }) {
      frontmatter {
        title
        get_in_touch_with_us {
          header
          subheader
          email
          phone
        }
        metadata {
          title
          description
        }
        _PARENT
      }
    }
  }
`;

gatsby-node.js

const path = require('path');

// Implement the Gatsby API “createPages”. This is called once the
// data layer is bootstrapped to let plugins create pages from data.
exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions;

  return graphql(`
    query General {
      allMarkdownRemark {
        edges {
          node {
            id
            frontmatter {
              templateKey
              _PARENT
            }
          }
        }
      }
    }
  `).then(result => {
    if (result.errors) {
      result.errors.forEach(e => console.error(e.toString()));
      return Promise.reject(result.errors);
    }

    const pages = result.data.allMarkdownRemark.edges;

    pages
      .filter(edge => edge.node.frontmatter.templateKey !== 'index')
      .forEach(edge => {
        const id = edge.node.id;
        const why_choose_us = edge.node.frontmatter.why_choose_us;
        const {
          node: {
            frontmatter: { templateKey },
          },
        } = edge;

        createPage({
          path: `/${templateKey}`,
          component: path.resolve(`src/templates/${templateKey}.js`),
          context: {
            id,
          },
        });
      });
  });
};

Let me know if you need anything else.

Thanks

DavidHe1127 commented 5 years ago

@appleJax Problems solved after following material-ui + gatsby example. But I am still confused with their solution.

    // This is needed in order to deduplicate the injection of CSS in the page.
    sheetsManager: new Map(),
    // This is needed in order to inject the critical CSS.
    sheetsRegistry: new SheetsRegistry(),

I assume css are collected from components and injected into html during build time? Why do we need to dedupe here?

vickywane commented 5 years ago

@DavidHe1127 can you please show a snippet on how you imported and used withRoot ?. .. Am having errors using it with gatsby

DavidHe1127 commented 5 years ago

@vickywane what errors did you get?

vickywane commented 5 years ago

Sorry for the late reply .

I used the withRoot from the MUI repo to wrap it but i get this error .

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

It worked with a next js project i recently concluded( without using withRoot)

martin2844 commented 5 years ago

Im really just starting out with web dev, so I might be wrong. Having said that, Im building a gatsby site with Material UI and was experiencing the same issues. Styles not being injected when loading site at index page, but after going to another page where the same component can be found styles are loaded correctly, and if you go back to the index page you now see everything correctly.

I tried a couple of things, but what solved it was adding to my gatsby-config.js the following:

{ resolve: 'gatsby-plugin-material-ui', // If you want to use styled components you should change the injection order. options: { stylesProvider: { injectFirst: true, }, },

Dont really understand much, except mi site is now working correctly.

chri5bot commented 5 years ago

Hey, I have the same issue, but I just follow this guide https://www.gatsbyjs.org/packages/gatsby-plugin-styled-components/?=styled and now my website is working correctly.

Regards.

Jennykuma commented 5 years ago

Hey, I have the same issue, but I just follow this guide https://www.gatsbyjs.org/packages/gatsby-plugin-styled-components/?=styled and now my website is working correctly.

Regards.

Hey, thanks @chri5bot ! My site is working because of this as well.

KoolP commented 5 years ago

Im really just starting out with web dev, so I might be wrong. Having said that, Im building a gatsby site with Material UI and was experiencing the same issues. Styles not being injected when loading site at index page, but after going to another page where the same component can be found styles are loaded correctly, and if you go back to the index page you now see everything correctly.

I tried a couple of things, but what solved it was adding to my gatsby-config.js the following:

{ resolve: 'gatsby-plugin-material-ui', // If you want to use styled components you should change the injection order. options: { stylesProvider: { injectFirst: true, }, },

Dont really understand much, except mi site is now working correctly.

This works. In my case I had a double call for the material UI plugin in the gatsby.config.js and the first one was not in this format {response... Newest version of the plugin and this works.

marciplan commented 5 years ago

Hey, I have the same issue, but I just follow this guide https://www.gatsbyjs.org/packages/gatsby-plugin-styled-components/?=styled and now my website is working correctly.

Regards.

Hey, thank you so much for this :)

itujono commented 5 years ago

Can confirm, as @chri5bot stated, if you are using styled-components, installing gatsby-plugin-styled-components as well as babel-plugin-styled-components should do the tricks.

Had similar issue for like a week, and now my app is working like a charm. 👍🏼

ffael commented 4 years ago

Can confirm, as @chri5bot stated, if you are using styled-components, installing gatsby-plugin-styled-components as well as babel-plugin-styled-components should do the tricks.

Had similar issue for like a week, and now my app is working like a charm. 👍🏼

Had the same problem with styled-components and installing the gatsby-plugin-styled-components did the trick for me!

rebeccapeltz commented 4 years ago

I'm using a starter project that uses gatsby-plugin-emotion, but seeing that content is rendered unstyled for a second or 2. I tried installing the gatby-plugin-styled-components on top of that and it didn't seem to have any effect. Would the emotion plugin interfere with this? The starter errors out when I switch everything to styled-components as they're using some emotion features.

vickywane commented 4 years ago

@rebeccapeltz i really would suggest you stick to either emotion or styled-components. I figured out some styling bugs only pop up after you run build.

Your comment wasnt 100% clear. Do you have the styling error?

rebeccapeltz commented 4 years ago

I removed the styled-components code so it's just emotion. In this video I refresh several times and you can see a flash where it shows the page unstyled content and even the icons are rendered unstyled. I like this starter and I'm glad to find this problem so I can better understand how to make this work. I don't see this problem running the app locally so it's something to do with the server-side render. https://www.screencast.com/t/2HrXbcAs

vickywane commented 4 years ago

Hi,

I just saw where you said you remove all styled-components.

I dont have much experience using emotion with gatsby. Do follow the steps above of you consider using styled-components.

rebeccapeltz commented 4 years ago

Thanks for your attention here. After adding the styled component dependencies and adding the plugin to gatsby.config.js, would I need to replace all the emotion code and remove the emotion plugin? Or is it possible to run them on the same project? In the responses above, there's no mention of code changes. It may be that I can't fix this starter app without some significant recoding. It does sound like I shouldn't expect to see the unstyled content though.

vickywane commented 4 years ago

I do not have much experience with emotion , however elements that are wrapped with styled-components shouldn't show as unstyled as you have shown.

I would advice you follow the documentation written at the plugin section of the docs and you make little incremental changes i.e trying styled-components on a component and see if it renders that same way before migrating everything.

You can leave a link to repository and i might have a look after my working hours.

Also just to test, you can comment out the emotion plugin line in your config file , dont use the emotion styles for a particular component and try that component with styled-components and see if that works out.

rebeccapeltz commented 4 years ago

I found this starter project. If you add some markdown under the content and push to netlify you'll get the same effect. I tried removing emotion and replacing with styled-components, but there are some code dependencies I don't understand. Do you know of another starter that produces a similar git book? https://github.com/hasura/gatsby-gitbook-starter. I really appreciate you looking into this.

chri5triplemint commented 4 years ago

Hey @rebeccapeltz. I used the same git-book https://condescending-pike-950821.netlify.app/ and I have the same problem. I was reading about this topic but I didn't find a good solution . Maybe It would help with something:

https://github.com/emotion-js/emotion/issues/1332

Could you try it?

let me know if it works.

Regards :)

rebeccapeltz commented 4 years ago

I took out the react-helmet and it did seem unnecessary. When I deploy to netlify it still shows the unstyled lists before final render. I think if you're looking at 'gatsby develop', you won't see the problem, only when deployed on netlify. I'm trying to remove emotion from the Layout component. It's using an Emotion Theme Provider to give the feature that allows you to control background color. Thanks for finding the fact that you don't need to React-Helmet!

anisometropie commented 4 years ago

Had the same problem, with style not loading initially (it created a really awful visual glitch).

I use JSS to style my components, and because the gatbsy-plugin-jss doesn’t offer ways to configure my own global style sheet, at some point I completely removed the plugin from my project and did the configuration manually in my gatsby-browser.js

I added the plugin back. Adding that one line in gatsby-config.js, without changing anything to my gatsby-browser.js solved the problem as well:

plugins: [
  … // other plugins
  `gatsby-plugin-jss`
  ]
ecbrodie commented 4 years ago

I had some issues on my application that uses gatsby alongside MUI (gatsby-theme-material-ui). My problem turned out to be with loading a value from a cookie in order to render a component. I solved the issue by loading the cookie value instead from a useEffect hook. This approach was based on this issue comment: https://github.com/gatsbyjs/gatsby/issues/8560#issuecomment-599154610

adstr123 commented 4 years ago

Hey, I have the same issue, but I just follow this guide https://www.gatsbyjs.org/packages/gatsby-plugin-styled-components/?=styled and now my website is working correctly.

Regards.

I also had this issue and resolved it by simply installing gatsby-plugin-styled-components as per the above message.

The issue arose around the time I installed react-awesome-reveal, which is a library that uses Emotion in the background to do the animations on reveal. Another user above referred to Emotion during their issue so I suppose this is related.

r0mankon commented 3 years ago

Im really just starting out with web dev, so I might be wrong. Having said that, Im building a gatsby site with Material UI and was experiencing the same issues. Styles not being injected when loading site at index page, but after going to another page where the same component can be found styles are loaded correctly, and if you go back to the index page you now see everything correctly.

I tried a couple of things, but what solved it was adding to my gatsby-config.js the following:

{ resolve: 'gatsby-plugin-material-ui', // If you want to use styled components you should change the injection order. options: { stylesProvider: { injectFirst: true, }, },

Dont really understand much, except mi site is now working correctly.

Thank you so much for this!

kwhitejr commented 3 years ago

This is a great thread! I'm wondering if anyone experiencing this problem with emotion has come up with a reliable solution? My problem seems isolated to components that combine css and withTheme from @emotion/react and styled from @emotion/styled. Per the common descriptions here, initially many components load unstyled, but styles pop-in on browser refresh.

Each component looks like

components
|- AppBar
  |- AppBar.jsx // exports UnstyledAppBar functional component
  |- styles.js // exports styles() which returns css`...`
  |- index.js // exports withTheme(styled(UnstyledAppBar)`${styles}`), i.e. a styled component with theme injected

Further, the styles that fail to work on first load are the styles derived from the theme prop.

In other words

// styles.js
const styles = (props) => {
  const { theme } = props;

  return css`
    margin-left: 24px;
    margin-right: ${spacingSizeMedium}; // evaluates to 24px
  `;
}

The margin-left style renders immediately but margin-right requires refresh.

This issue does not appear in Gatsby development mode, but occurs in Gatsby builds.