Skip to content

Extracted static stylesheet file into header #2534

@radeno

Description

@radeno

Many issues are trying to solve extracting CSS file as separate link for production env. Eg: #1479 #1396 #1615

There is another solution what is works, and is quite elegant. (maybe isn't 😄 )
This solution works for Next v3 beta and Docker. My source directory is src and build directory is one level up .next. We are using separated style files in /style directory with index.css where are all css files imported.

Required Webpack plugins and loaders

  • extract-text-webpack-plugin
  • css-loader
  • raw-loader
  • postcss-loader

next.config.js

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  distDir: '../.next',
  webpack: (config, { dev }) => {
    config.module.rules.push({
      test: /\.css$/,
      loader: 'emit-file-loader',
      options: {
        name: 'dist/[path][name].[ext]'
      }
    });

    if (!dev) {
      config.module.rules.push({
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          use: [
            {
              loader: 'css-loader',
              options: {
                importLoaders: 2,
                modules: false,
                url: true,
                sourceMap: false,
                minimize: true,
                localIdentName: false
                  ? '[name]-[local]-[hash:base64:5]'
                  : '[hash:base64:5]'
              }
            },
            { loader: 'postcss-loader' }
          ]
        })
      });

      config.plugins.push(new ExtractTextPlugin('app.css'));
    } else {
      config.module.rules.push({
        test: /\.css$/,
        use: [{ loader: 'raw-loader' }, { loader: 'postcss-loader' }]
      });
    }

    return config;
  }
};

File is emitted in build directory and then is processed with css-loader and postcss-loader. After processing is extracted next to app.js file.
Do not use babel-loader for global as in this example: https://github.com/zeit/next.js/tree/v3-beta/examples/with-global-stylesheet It is not neccessary and doesn't work well with postcss-calc.

Docker

In dockerfile, copy generated app.css to static directory
RUN mkdir src/static/styles && cp .next/app.css src/static/styles/app.css

Header link for production, style for development

pages/_document.js we also used React-Helmet, so this example is

import Document, { Head, Main, NextScript } from 'next/document';
import stylesheet from '../styles/index.css';

export default class extends Document {
  render() {
    return (
      <html>
        <Head>
          {process.env.NODE_ENV == 'production'
            ? <link
                rel="stylesheet"
                type="text/css" 
                href={`/static/styles/app.css?${this.props.__NEXT_DATA__
                  .buildStats['app.js'].hash}`}
              />
            : <style
                global
                dangerouslySetInnerHTML={{
                  __html: stylesheet
                }}
              />}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

Link to app.css is appended with querystring with same hash as is used in app.js path for disabling cache after redeploy.

What we can do better?

Next Server define route for production app.js file (https://github.com/zeit/next.js/blob/v3-beta/server/index.js#L151). This example should be more elegant if we can define route for app.css

Then this could be automated or at least we should have better links for app.css file. Ex:

<link
  rel="stylesheet"
  type="text/css"
  href={`/_next/${this.props.__NEXT_DATA__.buildStats['app.js'].hash}/app.css`}
/>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions