Skip to main content
Light Dark System
Get ready for more awesome! Web Awesome, the next iteration of Shoelace, is on Kickstarter. Read Our Story

Integrating with NextJS

This page explains how to integrate Shoelace with a NextJS app.

Instructions - Next 12.1.6

  • Node: 16.13.1
  • NextJS: 12.1.6
  • Shoelace: 2.0.0-beta.74

To get started using Shoelace with NextJS, the following packages must be installed.

yarn add @shoelace-style/shoelace copy-webpack-plugin next-compose-plugins next-transpile-modules

Enabling ESM

Because Shoelace utilizes ESM, we need to modify our package.json to support ESM packages. Simply add the following to your root of package.json:

"type": "module"

There’s one more step to enable ESM in NextJS, but we’ll tackle that in our Next configuration modification.

Importing the Default Theme

The next step is to import Shoelace’s default theme (stylesheet) in your _app.js file:

import '@shoelace-style/shoelace/dist/themes/light.css';

Defining Custom Elements

After importing the theme, you’ll need to import the JavaScript files for Shoelace. However, this is a bit tricky to do in NextJS thanks to the SSR environment not having any of the required browser APIs to define endpoints.

We’ll want to create a component that uses React’s useLayoutEffect to add in the custom components before the first render:

function CustomEls({ URL }) {
  // useRef to avoid re-renders
  const customEls = useRef(false);

  useLayoutEffect(() => {
    if (customEls.current) {
      return;
    }

    import('@shoelace-style/shoelace/dist/utilities/base-path').then(({ setBasePath }) => {
      setBasePath(`${URL}/static/static`);

      // This imports all components
      import('@shoelace-style/shoelace/dist/react');
      // If you're wanting to selectively import components, replace this line with your own definitions

      // import("@shoelace-style/shoelace/dist/components/button/button");
      customEls.current = true;
    });
  }, [URL, customEls]);

  return null;
}

You may be wondering where the URL property is coming from. We’ll address that in the next few sections.

Using Our New Component In Code

While we need to use useLayoutEffect for the initial render, NextJS will throw a warning at us for trying to use useLayoutEffect in SSR, which is disallowed. To fix this problem, we’ll conditionally render the CustomEls component to only render in the browser

function MyApp({ Component, pageProps, URL }) {
  const isBrowser = typeof window !== 'undefined';
  return (
    <>
      {isBrowser && <CustomEls URL={URL} />}
      <Component {...pageProps} />
    </>
  );
}

Instructions - Next 14.2.4

  • Node: v20.11.1
  • NextJS: 14.2.4
  • Shoelace: 2.15.1

To get started using Shoelace with NextJS, the following packages must be installed.

yarn add @shoelace-style/shoelace

Importing the Default Theme

The next step is to import Shoelace’s default theme (stylesheet) in your _app.js file:

import '@shoelace-style/shoelace/dist/themes/light.css';

Defining Custom Elements

After importing the theme, you’ll need to import the JavaScript files for Shoelace. However, this is a bit tricky to do in NextJS thanks to the SSR environment not having any of the required browser APIs to define endpoints.

We’ll want to create a component that uses React’s useLayoutEffect to add in the custom components before the first render:

function CustomEls({ URL }) {
  // useRef to avoid re-renders
  const customEls = useRef(false);

  useLayoutEffect(() => {
    if (customEls.current) {
      return;
    }

    import('@shoelace-style/shoelace/dist/utilities/base-path').then(({ setBasePath }) => {
      setBasePath(`${URL}/static/static`);

      // This imports all components
      import('@shoelace-style/shoelace/dist/react');
      // If you're wanting to selectively import components, replace this line with your own definitions

      // import("@shoelace-style/shoelace/dist/components/button/button");
      customEls.current = true;
    });
  }, [URL, customEls]);

  return null;
}

You may be wondering where the URL property is coming from. We’ll address that in the next few sections.

Using Our New Component CustomEls In Code

While we need to use useLayoutEffect for the initial render, NextJS will throw a warning at us for trying to use useLayoutEffect in SSR, which is disallowed. To fix this problem, we’ll conditionally render the CustomEls component to only render in the browser

function MyApp({ Component, pageProps, URL }) {
  const isBrowser = typeof window !== 'undefined';
  return (
    <>
      {isBrowser && <CustomEls URL={URL} />}
      <Component {...pageProps} />
    </>
  );
}

Importing Shoelace Components

After importing the customElement in your _app.tsx e.g., you now can import the components.

We will need to mark the component as ‘use client’

'use client';
import dynamic from 'next/dynamic';

const SlButton = dynamic(() => import('@shoelace-style/shoelace/dist/react/button'), {
  loading: () => <p>Loading...</p>,
  ssr: false
});

export default function Sample() {
  return <SlButton>Button</SlButton>;
}

Environmental Variable

However, to make setBasePath() work as-expected, we need to know where the file is hosted. To do this, we need to set environmental variables. Create a .local.env file and put the following inside:

BASE_URL="localhost:3000"

Then, modify your MyApp class in _app.js to pass this process environment into your render:

MyApp.getInitialProps = async context => {
  const URL = process.env.BASE_URL;

  return {
    URL
  };
};

webpack Config

Next we need to add Shoelace’s assets to the final build output. To do this, modify next.config.js to look like this.

import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import CopyPlugin from 'copy-webpack-plugin';
import withPlugins from 'next-compose-plugins';
import withTM from 'next-transpile-modules';

const withTMCompiled = withTM(['@shoelace-style/shoelace']);

const __dirname = dirname(fileURLToPath(import.meta.url));

export default withPlugins([withTMCompiled], {
  // This is required for ESM to work properly with Shoelace
  experimental: { esmExternals: 'loose' },
  webpack: config => {
    config.plugins.push(
      new CopyPlugin({
        patterns: [
          {
            from: resolve(__dirname, 'node_modules/@shoelace-style/shoelace/dist/assets/icons'),
            to: resolve(__dirname, 'static/icons')
          }
        ]
      })
    );
    return config;
  }
});

Additional Resources