Next.js: Flash of Unstyled Content with <style jsx>

Created on 21 Feb 2017  路  27Comments  路  Source: vercel/next.js

I am using <style jsx> for most things on the site.

I am also using the <Head> component in my layout to inline a custom <style> tag that includes my minified reset and other items.

My question is, both <Head> and <style jsx> is not part of the rendered HTML from the server. It seems to me that because the page is declarative that it should be rendered as part of the Universal Rendering and already have the style tags for <style jsx> and <Head> present at the top of the file.

What is the best way to handle this besides reverting to outside CSS file and ripping all styles out of the <style jsx> tags?

bug

Most helpful comment

@khrome83 this is the functioning version of your example _document.js. The docs are wrong in this case. styled-jsx should be flushed and passed. I'll add this.

Note: you should use <Head> because this tag renders both styled-jsx and the merged next/head

import Document, { Head, Main, NextScript } from 'next/document';
import flush from 'styled-jsx/server'

export default class MyDocument extends Document {
  static async getInitialProps ({ renderPage }) {
    const { html, head } = renderPage()
    const styles = flush()
    return { html, head, styles }
  }

  render() {
    return (
      <html lang="en">
        <Head>
          <link rel="manifest" href="static/manifest.json" />

          <meta charSet="utf-8" />
          <meta name="robots" content="index, follow" />
          <meta name="author" content="Author" />
          <meta name="viewport" content="initial-scale=1.0, width=device-width" />
          <meta name="mobile-web-app-capable" content="yes" />
          <meta name="apple-mobile-web-app-capable" content="yes" />
          <meta name="application-name" content="Application" />
          <meta name="apple-mobile-web-app-title" content="Application" />
          <meta name="theme-color" content="#00b6b2" />
          <meta name="msapplication-navbutton-color" content="#00b6b2" />
          <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
          <meta name="msapplication-starturl" content="/" />
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
          <meta name="format-detection" content="telephone=no" />

          <link rel="icon" type="image/png" sizes="192x192" href="static/icons/icon.png" />
          <link rel="apple-touch-icon" type="image/png" sizes="192x192" href="static/icons/icon.png" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

All 27 comments

<style jsx> should definitely be rendered server side. Can you provide an example?

Question - Should it be rendered in the NEXT_DATA object in the foot, or inline in the head the way it shows?

Should render around line 1. See view-source:https://next-news.now.sh/

Yeah it is not.

So I can't share code because of the project sensitivity until our launch. Not my call.

What I can say is this is my current structure.

  • I do have a <head> tag within _document.js that has custom meta tags.
  • I also have <Head> tag (component) within my Default.js layout that has both inline styles and additional Meta tags.

    • Both the styles and the open graph tags here are not rendered in the output

  • I am then using Container components that pull in Presentational components that contain the <style jsx>

So index.js (page) wraps containers with a layout. Containers point to presentation components where the <style jsx> is laid out.

I am also using background-color: ${color.background};.

Would dependencies on a file include for global values prevent this?

Ok I found the problem. Not sure how to resolve this though.

Using a custom _document.js made it so that it would not render the following -

  1. Inline <style> tags within a <Head> component
  2. Meta tags within a <Head> component
  3. Any CSS included via <style jsx>

Once I renamed _document.js to hide it from Next, it rendered correctly, minus what I added to the _document.js.

Sample of the custom _document.js

import Document, { Main, NextScript } from 'next/document';

export default class MyDocument extends Document {
  static async getInitialProps({ renderPage }) {
    const page = renderPage();
    return { ...page };
  }

  render() {
    return (
      <html lang="en">
        <head>
          <link rel="manifest" href="static/manifest.json" />

          <meta charSet="utf-8" />
          <meta name="robots" content="index, follow" />
          <meta name="author" content="Author" />
          <meta name="viewport" content="initial-scale=1.0, width=device-width" />
          <meta name="mobile-web-app-capable" content="yes" />
          <meta name="apple-mobile-web-app-capable" content="yes" />
          <meta name="application-name" content="Application" />
          <meta name="apple-mobile-web-app-title" content="Application" />
          <meta name="theme-color" content="#00b6b2" />
          <meta name="msapplication-navbutton-color" content="#00b6b2" />
          <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
          <meta name="msapplication-starturl" content="/" />
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
          <meta name="format-detection" content="telephone=no" />

          <link rel="icon" type="image/png" sizes="192x192" href="static/icons/icon.png" />
          <link rel="apple-touch-icon" type="image/png" sizes="192x192" href="static/icons/icon.png" />
        </head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

This is mostly because of meta tags on all pages. I would like to keep this structure, but as a workaround, I could export a component that returns a global HOC for <Head> and include that in any Layout.

I would need to have 2 <Head> components output in the Layout. Is that valid?

This feels like a work around and not the right way to handle this.

HOC is always preferred to overriding document.

Ok, I can do that. Still seems like this is a bug with _document.js?

Or is my code missing something needed that the example does not show?

Other issues have already discussed the fact that style shouldn't be put into _document.js. Use a HOC instead. Might be worth documenting this limitation better.

This is a bug.

@khrome83 this is the functioning version of your example _document.js. The docs are wrong in this case. styled-jsx should be flushed and passed. I'll add this.

Note: you should use <Head> because this tag renders both styled-jsx and the merged next/head

import Document, { Head, Main, NextScript } from 'next/document';
import flush from 'styled-jsx/server'

export default class MyDocument extends Document {
  static async getInitialProps ({ renderPage }) {
    const { html, head } = renderPage()
    const styles = flush()
    return { html, head, styles }
  }

  render() {
    return (
      <html lang="en">
        <Head>
          <link rel="manifest" href="static/manifest.json" />

          <meta charSet="utf-8" />
          <meta name="robots" content="index, follow" />
          <meta name="author" content="Author" />
          <meta name="viewport" content="initial-scale=1.0, width=device-width" />
          <meta name="mobile-web-app-capable" content="yes" />
          <meta name="apple-mobile-web-app-capable" content="yes" />
          <meta name="application-name" content="Application" />
          <meta name="apple-mobile-web-app-title" content="Application" />
          <meta name="theme-color" content="#00b6b2" />
          <meta name="msapplication-navbutton-color" content="#00b6b2" />
          <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
          <meta name="msapplication-starturl" content="/" />
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
          <meta name="format-detection" content="telephone=no" />

          <link rel="icon" type="image/png" sizes="192x192" href="static/icons/icon.png" />
          <link rel="apple-touch-icon" type="image/png" sizes="192x192" href="static/icons/icon.png" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

@khrome83 #1287

@timneutkens awesome, thank you for this!

I wax having issues getting server-side rendering working with rebass (which uses styled-components) and <style global jsx> tags.

I solved my problems by using a custom _document.js with a combination of the document in the styled-components example here and @timneutkens answer above.

You can see my version here

https://github.com/styled-components/styled-components/issues/162 && https://github.com/zeit/next.js/issues/184

Hey @timneutkens, I had to do the follow (per #3434) to get this working:

import Document, { Head, Main, NextScript } from 'next/document'
import flush from 'styled-jsx/server'

export default class MyDocument extends Document {
  static getInitialProps({ renderPage }) {
    const { html, head, errorHtml, chunks } = renderPage()
    const styles = flush()
    return { html, head, errorHtml, chunks, styles }
  }

  render() { ... }
}

Did something change? Also, were the docs updated somewhere to reflect this?

Only thing mentioned here is:

styled-jsx is included with Next.js by default.

Also, I notice that I still get a FOUC if I use <head> instead of <Head> in _document.js :thinking:

<head> should never be used. <Head> implements it. <Head> also implements server side rendering of next/head and rendering of the styles prop returned from getInitialProps.

Also, the docs mention using the parent class its getInitialProps call: https://github.com/zeit/next.js#custom-document

This also appears to be an issue when using styled-components. Will attempt to get a minimal example up soon. Any suggestions?

I have the same FOUC issue using the https://github.com/zeit/next.js/tree/canary/examples/with-next-css example

Upudate: FOUC happens to me when using styled-jsx^2.2.7, if I drop it to 2.2.6 FOUC is gone

@hugotox - maybe the issue needs filed with styled-jsx repo? Not sure if its related to next or not. This did work for me, have not validated your issue since we have not upgraded yet.

@hugotox did you try the custom document outlined here: https://github.com/zeit/next.js/issues/1236#issuecomment-362872266?

Yes, I did. The only way around it was downgrade styled-jsx to 2.2.6.
My issue must be something else though since I'm using styled-jsx and next-css at the same time.

For me even styled-jsx downgrade did not work :( And none of the suggested webpack configs :(

yeah I'm getting this issue too.

I had this issue when I was incorrectly using <style> inside <Head>.

Something like this:

const Layout = ({ children }) => (
    <div className="Layout">
        <Head>
            <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
            <link
                rel="stylesheet"
                href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css"
            />
          <style>
            {`
                body,
                html {
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
                        'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
                        'Droid Sans', 'Helvetica Neue', sans-serif;
                }
            `}
        </style>
        </Head>
        <Header />
        {children}
    </div>
);

I fixed it by making the change to use style global jsx:

const Layout = ({ children }) => (
    <div className="Layout">
        <Head>
            <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
            <link
                rel="stylesheet"
                href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css"
            />
        </Head>
        <style global jsx>
            {`
                body,
                html {
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
                        'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
                        'Droid Sans', 'Helvetica Neue', sans-serif;
                }
            `}
        </style>
        <Header />
        {children}
    </div>
);
Was this page helpful?
0 / 5 - 0 ratings