critical-style-loader

A webpack loader for rendering critical path CSS on the server with utilities to avoid client side style-loader style duplications.

Usage no npm install needed!

<script type="module">
  import criticalStyleLoader from 'https://cdn.skypack.dev/critical-style-loader';
</script>

README

Critical Style Loader

Dynamically generate critical path css by gathering all imported CSS modules used during a SSR request. Obtain the resulting CSS as a string to be rendered by your server application to avoid FOUC in the client.

Includes utilities to avoid duplicated styles in the browser by filtering any style-loader handled CSS that has been pre-rendered as critical CSS.

Inspirations

Install

npm install critical-style-loader

Usage

webpack.server.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: 'critical-style-loader',
          },
          {
            loader: 'css-loader',
            options: {
              modules: true,
            }
          }
        ],
      },
    ],
  },
};

webpack.client.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader',
            options: {
              transform: 'node_modules/critical-style-loader/lib/filterCriticalCss.js',
            },
          },
          {
            loader: 'css-loader',
            options: {
              modules: true,
            }
          }
        ],
      },
    ],
  },
};

server.js

import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StyleContext, CriticalCssBuilder } from 'critical-style-loader/lib';
import App from 'components/app';

const app = express();
// ... configure express

app.use((req, res) => {
  const cssBuilder = new CriticalCssBuilder();
  const html = renderToString(
    <StyleContext.Provider value={cssBuilder.addCss}>
      <App />
    </StyleContext.Provider>
  );

  res.render('app', {
    html,
    criticalCss: cssBuilder.getCss(),
    criticalCssMap: cssBuilder.getEncodedMap(),
  });
});

app.listen();

app.ejs

<!doctype html>
<html lang="en">
<head>
    <style id="critical-css"><%- criticalCss %></style>
</head>
<body>
<div id="root"><%- html %></div>

<script id="preload">
  <%- criticalCssMap %>;
</script>
</body>
</html>

components/app.js

import React from 'react';
import { withStyles } from 'critical-style-loader/lib';
import styles from './app.css';

const App = () => (
 <h1 style={styles.heading}>Hello World!</h1> 
);

const wrapWithStyles = withStyles(styles);
export default wrapWithStyles(App);

client.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/app';

ReactDOM.hydrate(
  <App />,
  document.getElementById('root')
);