---
meta:
  title: Integrating with NextJS
  description: This page explains how to integrate Shoelace with a NextJS app.
---

# Integrating with NextJS

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

:::tip
This is a community-maintained document. Please [ask the community](/resources/community) if you have questions about this integration. You can also [suggest improvements](https://github.com/shoelace-style/shoelace/blob/next/docs/tutorials/integrating-with-nextjs.md) to make it better.
:::

There are 2 guides available:

- [Usage with App Router NextJS 14](#usage-with-app-router-nextjs-14)
- [Usage with Pages Router NextJS 12](#usage-with-pages-router-nextjs-12)

## Usage with App Router (NextJS 14)

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

### Working with ESM

If you haven't already, create your NextJS app. You can find the documentation for that here: <https://nextjs.org/docs/getting-started/installation>

After you've created your app, the first step to using Shoelace is modifying your `package.json` to have `"type": "module"` in it since Shoelace ships ES Modules.

```json
// package.json
{
  "type": "module"
}
```

### Installing packages

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

```bash
npm install @shoelace-style/shoelace copy-webpack-plugin
```

Shoelace for obvious reasons, and the `copy-webpack-plugin` will be used later for adding our icons to our `public/` folder.

### Modifying your Next Config

We'll start with modifying our `next.config.js` to copy Shoelace's assets and to properly work with ESM.

Here's what your `next.config.js` should look like:

### NextJS 14 Webpack Config

In order to add Shoelace's assets to the final build output, we need to modify `next.config.js` to look like this.

```javascript
// next.config.js

import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import CopyPlugin from 'copy-webpack-plugin';

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

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: { esmExternals: 'loose' },
  webpack: (config, options) => {
    config.plugins.push(
      new CopyPlugin({
        patterns: [
          {
            from: resolve(__dirname, 'node_modules/@shoelace-style/shoelace/dist/assets/'),
            to: resolve(__dirname, 'public/shoelace-assets/assets/')
          }
        ]
      })
    );
    return config;
  }
};

export default nextConfig;
```

:::tip
This will copy the files from `node_modules/@shoelace-style/shoelace/dist/assets` into your `public/shoelace-assets` folder on every development serve or build. You may want to avoid committing these into your repo. To do so, simply add `public/shoelace-assets` into your `.gitignore` folder
:::

### Importing the Shoelace's CSS (default theme)

Once we've got our webpack config / next config setup, lets modify our `app/layout.tsx` to include Shoelace's default theme.

```javascript
// app/layout.tsx
import './globals.css';
import '@shoelace-style/shoelace/dist/themes/light.css';
// We can also import the dark theme here as well.
// import "@shoelace-style/shoelace/dist/themes/dark.css";
```

### Writing a "setup" component

Now, we need to create a `ShoelaceSetup` component that will be a client component in charge of setting the `basePath` for our assets / icons.

To do so, create a file called `app/shoelace-setup.tsx`

```typescript
'use client';
// ^ Make sure to have 'use client'; because `setBasePath()` requires access to `document`.

import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path.js"

export default function ShoelaceSetup({
  children,
}: {
  children: React.ReactNode
}) {
  setBasePath(`/shoelace-assets/`);
  return <>{children}</>
}
```

:::warning
Don't forget to mark your Shoelace components and Shoelace setup with 'use client'.
:::

Then we'll add this setup component into `app/layout.tsx`

Our `layout.tsx` Should now look something like this:

```diff
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
+ import "@shoelace-style/shoelace/dist/themes/light.css";

+ import ShoelaceSetup from "./shoelace-setup";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
+         <ShoelaceSetup>
            {children}
+         </ShoelaceSetup>
      </body>
    </html>
  );
}
```

### Writing components that use Shoelace

Now that we have the setup in place, we can write an `app/page.tsx` to use Shoelace components in combination with the `dynamic()` component loader from NextJS.

Here's what that would look like, do note the `"use client";` at the top of the component is required.

```typescript
// app/page.tsx
'use client';

import React from "react";
import dynamic from "next/dynamic";

const SlButton = dynamic(
  // Notice how we use the full path to the component. If you only do `import("@shoelace-style/shoelace/dist/react")` you will load the entire component library and not get tree shaking.
  () => import("@shoelace-style/shoelace/dist/react/button/index.js"),
  {
    loading: () => <p>Loading...</p>,
    ssr: false,
  },
);

const SlIcon = dynamic(
  () => import("@shoelace-style/shoelace/dist/react/icon/index.js"),
  {
    loading: () => <p>Loading...</p>,
    ssr: false,
  },
);

export default function Home() {
  return (
    <main>
      <SlButton>Test</SlButton>
      <SlIcon name="gear" />
    </main>
  );
}
```

Now you should be up and running with NextJS + Shoelace!

If you're stuck, there's an [example repo here](https://github.com/konnorRogers/shoelace-nextjs-lazy) you can checkout.

## Usage with Pages Router (NextJS 12)

- 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.

```bash
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:

```css
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`](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) to add in the custom components before the first render:

```javascript
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;
}
```

:::tip
If we use `useEffect` instead of `useLayoutEffect`, the initial render will occur with the expected `sl-` props applied, but the subsequent render (caused by the `useEffect`) will remove those props as the custom components initialize. We _must_ use `useLayoutEffect` to have expected behavior
:::

:::tip
This will import all Shoelace components for convenience. To selectively import components, refer to the [Using webpack](/getting-started/installation#using-webpack) section of the docs.
:::

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

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

## Additional Resources

- There is a third-party [example repo](https://github.com/crutchcorn/nextjs-shoelace-example), courtesy of [crutchcorn](https://github.com/crutchcorn), available to help you get started using Pages Router.
- There is an [example repo](https://github.com/konnorRogers/shoelace-nextjs-lazy) here using the App Router.
