--- 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: 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 ( + {children} + ); } ``` ### 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: () =>

Loading...

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

Loading...

, ssr: false, }, ); export default function Home() { return (
Test
); } ``` 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 && } ); } ``` ## 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.