Next.js: <Head> content rendered in reverse order than declared

Created on 15 Jan 2018  路  25Comments  路  Source: vercel/next.js

When rendering content to the <Head> component, I found out the order of the Head content is actually reverse than what I expected.

Expected Behavior

I'd assume when declaring a <Head> inside the _document.js file, this content would show before content declared inside components further in the page lifecycle.

Current Behavior

Right now it seems <Head> content is being shifted. So tags declared later are being rendered first in the head.

Context

This can cause issues when trying to define resources to preload if scripts like GA or GTM are being executed first by the browser.

Your Environment

| Tech | Version |
|---------|---------|
| next | ^4.1.4 |
| node | 6 and 8 |
| OS | Ubuntu |
| browser | chrome |

bug

Most helpful comment

Same issue with a dead simpler example:

_document.js:

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

export default class MyDocument extends Document {
  render () {
    return (
      <html>
        <Head>
          <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

my-page.js

import React, { Component } from 'react'

export default class MyPage extends Component {
  render() {
    return (
      <div>
        <h1 className="msg">Yo</h1>
        <style jsx>{`
          body {
            font-family: arial;
          }
          .msg {
            color: red;
          }
          `}</style>
      </div>
    )
  }
}

The resulted head puts my component style before bootstrap so is really bad for customizing

screen shot 2018-02-28 at 1 49 28 pm

All 25 comments

We'd need an example of your _Document JS file to debug this issue.

Here:

// @flow
import * as React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
import cdn from '../modules/cdn';

export default class MyDocument extends Document {
    static getInitialProps({ renderPage }: *) {
        const sheet = new ServerStyleSheet();
        const page = renderPage(App => props => sheet.collectStyles(<App {...props} />));
        const styleTags = sheet.getStyleElement();
        return { ...page, styleTags };
    }

    render() {
        return (
            <html>
                <Head>
                    <meta name="viewport" content="width=device-width, initial-scale=1" />
                    <script
                        dangerouslySetInnerHTML={{
                            __html: `
                                window.CDN_URL = "${process.env.CDN_URL || ''}";
                            `,
                        }}
                    />
                    <link
                        rel="apple-touch-icon-precomposed"
                        href={cdn('/next_static/favicon-60.png')}
                    />
                    <link
                        rel="icon"
                        type="image/png"
                        href={cdn('/next_static/favicon.png')}
                    />
                    {this.props.styleTags}
                </Head>
                <body>
                    <div className="root">
                        <Main />
                    </div>
                    <NextScript />
                </body>
            </html>
        );
    }
}

Hi @SBoudrias, I'm testing your code inside the example basic-css with some little changes to see if I can replicate the error but it's showing the right order for me.

Here's the code I used to test _document.js

import * as React from 'react'
import Document, { Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static getInitialProps({ renderPage }) {
    const sheet = new ServerStyleSheet()
    const page = renderPage(App => props =>
      sheet.collectStyles(<App {...props} />)
    )
    const styleTags = sheet.getStyleElement()
    return { ...page, styleTags }
  }

  render() {
    return (
      <html>
        <Head>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <meta name="description" content="Testing head order"/>
          <meta name="author" content="John Doe"/>
          <script
            dangerouslySetInnerHTML={{
              __html: `
                window.CDN_URL = "${process.env.CDN_URL || 'HELLO'}";
              `
            }}
          />
          {this.props.styleTags}
          <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        </Head>

        <body>
          <div className="root">
            <Main />
          </div>
          <NextScript />
        </body>
      </html>
    )
  }
}

And here the html rendered in localhost:
captura

it looks good to me so maybe it's related to a different version of a package/node or to the code I omitted from _document.js

Versions of Next and React are the same as in the example, on Node 9.5.0 and windows 10

Same trouble. I can give you my exmples of code. And as I can see, you, @lfades, replicate it. Because in expected behaviour we are waiting what all head tag will apear just right after first meta tag with charset="utf-8" and before other scripts and tags.

for example my wrapper decorator for components:

export function withWrapper(Child) {
  class Wrapper extends Component {
    static async getInitialProps(...args){
      if (typeof Child.getInitialProps !== 'function') {
        return {}
      }
      let initialProps = Child.getInitialProps(...args)
      if (initialProps instanceof Promise) {
        initialProps = await initialProps
      }
      return initialProps
    }

    render() {
      return <div className={css.main}>
        <Head>
          <link rel="stylesheet" href="/_next/static/style.css"/>
        </Head>
        <Header/>
        <div className={css.container}>
          <Child {...this.props}/>
        </div>
      </div>
    }
  }
  return Wrapper
}

and my _document.js

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

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

  render() {
    return (
      <html lang="en" dir="ltr">
        {/*don't insert <head></head> otherwise nextjs will render head tag 2 times*/}
        {/*and don't insert anything in Head tag, bcs nextjs will insert it in the end of head tag*/}
        <Head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
          <link rel="stylesheet" type="text/css" href="/static/base.css"/>
          <link rel="icon" type="image/png" href="/static/favicon.png"/>
        </Head>
        <body>
          <Main/>
          <NextScript />
        </body>
      </html>
    )
  }
}

Expected Behavior: head tags from _document.js will be apear before head tags of wrapper.
But:
image

thats why I place that strange comment in the code ("dont insert code here")

So yeah. It is bug and example where we placing head tags in '_document.js' isn't good exmaples.

@JerryCauser confirmed, it's a bug, I'll see how to fix it

Same issue with a dead simpler example:

_document.js:

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

export default class MyDocument extends Document {
  render () {
    return (
      <html>
        <Head>
          <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

my-page.js

import React, { Component } from 'react'

export default class MyPage extends Component {
  render() {
    return (
      <div>
        <h1 className="msg">Yo</h1>
        <style jsx>{`
          body {
            font-family: arial;
          }
          .msg {
            color: red;
          }
          `}</style>
      </div>
    )
  }
}

The resulted head puts my component style before bootstrap so is really bad for customizing

screen shot 2018-02-28 at 1 49 28 pm

@hugotox The issue already has a fix but is not merged

Thank you! You guys are awesome

Is the fix for this issue already merged?

no 馃槩and I'm still getting that awful FOUC with latests version of Next

This issue reminded me of this thread: https://spectrum.chat/?t=0d91eec7-6a38-4943-98f6-0bb3a71d50f6
With the latest next, people seem to need to change the order of their head content. Would be nice to get that fixed if everyone can agree on the right order of things.

@timneutkens What is the status of this issue? Seems like an easy fix.
just do the following in https://github.com/zeit/next.js/blob/canary/packages/next/pages/_document.js#L127

return <head {...this.props}>
      {head}
      {page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}`} as='script' nonce={this.props.nonce} />}
       {children}
      <link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_app.js`} as='script' nonce={this.props.nonce} />
      <link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_error.js`} as='script' nonce={this.props.nonce} />
      {this.getPreloadDynamicChunks()}
      {this.getPreloadMainLinks()}
      {this.getCssLinks()}
      {styles || null}
    </head>

Or is there a reason why {children} is appended at the end?

Please merge this 馃檹

@lfades do you think that #5696 can be a remedy for the problem in Next.js 7?

@kachkaev The code has changed a lot since this pr has made, the issue was that the head content of the children was loaded at the start, if {children} includes it then yes.

next/static/styles need to be the last one..

This issue is the closest to what we're having with our static export.

The getCssLinks function of Next's Head component renders CSS link tags in the order they happen to be in an array.

  getCssLinks () {
    const { assetPrefix, files } = this.context._documentProps
    if(!files || files.length === 0) {
      return null
    }

    return files.map((file) => {
      // Only render .css files here
      if(!/\.css$/.exec(file)) {
        return null
      }

For some reasons, while doing a static export of our project, the CSS files are listed as follows:

[
  // Component specific styling
  'static/css/commons.4e0b01bb.chunk.css',
  // Global styling, like our custom bootstrap build
  'static/css/styles.685164fb.chunk.css'
]

Our custom build (global styling) is imported in the _app.jsx file.

// ~ is an alias to our assets directory
import '~/scss/main.scss';

It was working correctly in Next v6, but now, our global styling overrides our specific styling because of this new Head ordering.

Maybe I should open a new issue related to the static export?

Still having the same issue.
This is the only fix that works for now - https://spectrum.chat/next-js/general/control-the-placement-of-styles-chunk-css-in-nextjs7~0d91eec7-6a38-4943-98f6-0bb3a71d50f6

This issue reminded me of this thread: https://spectrum.chat/?t=0d91eec7-6a38-4943-98f6-0bb3a71d50f6
With the latest next, people seem to need to change the order of their head content. Would be nice to get that fixed if everyone can agree on the right order of things.

Since we only have 2 CSS files that are not in the right order, we ended writing a small sub-class of Next's Head:

export default class Head extends NextHead {
  getCssLinks() {
    return super.getCssLinks().reverse();
  }
}

Just a heads up that when using the static export of Next.js, my small patch above worked in some places, but breaks other pages, so we're rolling that back while evaluating the possibility of ditching the static export in favor of SSR.

@emileber just FYI there is no difference between static export and ssr in terms of rendering.

@timneutkens There is in terms of CSS compilation it seems.

It's literally using the same method to render renderToHTML() so I'm pretty sure there's something else going on.

It's not the HTML that's not rendered properly, it's the compiled CSS that is divided in different chunks in the static export that are in a different order than while using SSR in development mode.

While using SSR, we're only seeing one CSS bundle in the expected order. Then, after building a static export, there are two CSS bundles and the order of some selectors is now wrong.

Maybe the SSR mode in production will have the same problem in our project?! I'll check to be sure.

I feel like my problem is different from this issue, we thought at first that it was the order in which the linked CSS were rendered in the layout, but it seems more of a CSS plugin configuration problem (though we're not using/overriding anything special).

Sorry for the noise. I'll probably open an issue if I can isolate the problem correctly.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wagerfield picture wagerfield  路  3Comments

sospedra picture sospedra  路  3Comments

jesselee34 picture jesselee34  路  3Comments

ghost picture ghost  路  3Comments

timneutkens picture timneutkens  路  3Comments