Conditional global stylesheets for Next.js apps

image

Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed. The out of the box CSS support is amazing. It means that you could just put your css/scss files that you obtained from the designer at styles/scss/app.scss (the path or the file name don’t matter) and add the following into your _app.tsx (or your _app.jsx).

import '../styles/scss/app.scss';

This sets everything up including the compilation of SCSS files and hot reloading of the styles whenever you make some changes.

The Problem

But the shortcoming with this approach is that this import needs to be inside the global _app file. You cannot import it inside individual pages if you need to separate styles by page (or have a different stylesheet for some of the pages in your app). A common use case for this feature is if your app has two components, an admin interface where special users could perform some operations on your domain objects and a public interface which is visible to everyone else. Of course, you could ask the designer to provide a global stylesheet that works for both parts of the interface, but that is not always possible (and unnecessarily wasteful).

Styled JSX (and Babel and Webpack Configuration)

This is where Next.js’s styled-jsx support comes in. Bear in mind, it isn’t exactly meant to solve this issue, but it works great nonetheless. First start by installing the required packages (not that you don’t need sass related plugins if you have a CSS stylesheet):

yarn add styled-jsx @types/styled-jsx styled-jsx-plugin-sass node-sass-middleware sass

Next you need to configure babel for styled-jsx (again, you don’t need the sass specific stuff if you have only the CSS). Make sure your .babelrc looks something like this:

{
  "presets": [
    [
      "next/babel",
      {
        "styled-jsx": {
          "plugins": [
            [ "styled-jsx-plugin-sass", { "sassOptions": { "includePaths": ["./styles"] } }]
          ]
        }
      }
    ]
  ]
}

Now configure webpack (the config lives inside next.config.js) to allow loading css and scss files with the styled-jsx/webpack loader.

module.exports = {
  webpack(config, { defaultLoaders }) {
    config.module.rules.push({
      test: /\.(scss|css)$/,
      use: [
        defaultLoaders.babel,
        {
          loader: require('styled-jsx/webpack').loader,
          options: {
            type: 'global',
          },
        },
      ],
    });

    return config;
  },
};

The Style Component

Now we are ready to use the stylesheets inside our app. All we need is import the styles from the respective files and provide them through a regular react component.

import publicStyles from '../../../styles/public.scss';
import adminStyles from '../../../styles/admin.scss';

function AdminStyles(): React.ReactElement {
  return <style jsx global>{adminStyles}</style>;
}

function PublicStyles(): React.ReactElement {
  return <style jsx global>{publicStyles}</style>;
}

export default function AppStyles({ admin }: { admin: boolean }): React.ReactElement {
  if (admin) { return <AdminStyles />; }
  return <PublicStyles />;
}

You can now include this component as a part of your App exported from _app.tsx.

function App({ Component, pageProps }: Props): React.ReactElement {
  const router = useRouter();
  const isAdmin = router.pathname.startsWith('/admin');

  return (
    <>
      <Component {...pageProps} />
      <AppStyles admin={isAdmin} />
    </>
  );
}
Published 1 Nov 2020

I build mobile and web applications. Full Stack, Rails, React, Typescript, Kotlin, Swift
Pulkit Goyal on Twitter