From 325a9e2c4a343b188c828b3cdf37af253d73baeb Mon Sep 17 00:00:00 2001 From: Konnor Rogers Date: Wed, 10 Jul 2024 11:20:37 -0400 Subject: [PATCH] add docs for nextjs 14 (#2096) * add docs for nextjs 14 * prettier --- .../tutorials/integrating-with-nextjs.md | 365 ++++++++++-------- 1 file changed, 199 insertions(+), 166 deletions(-) diff --git a/docs/pages/tutorials/integrating-with-nextjs.md b/docs/pages/tutorials/integrating-with-nextjs.md index 38f7e165..6316cc25 100644 --- a/docs/pages/tutorials/integrating-with-nextjs.md +++ b/docs/pages/tutorials/integrating-with-nextjs.md @@ -12,7 +12,203 @@ This page explains how to integrate Shoelace with a NextJS app. 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. ::: -## Instructions - Next 12.1.6 +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 @@ -101,170 +297,7 @@ function MyApp({ Component, pageProps, URL }) { } ``` -## 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. - -```bash -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: - -```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 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 - -```javascript -function MyApp({ Component, pageProps, URL }) { - const isBrowser = typeof window !== 'undefined'; - return ( - <> - {isBrowser && } - - - ); -} -``` - -### 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' - -```javascript -'use client'; -import dynamic from 'next/dynamic'; - -const SlButton = dynamic(() => import('@shoelace-style/shoelace/dist/react/button'), { - loading: () =>

Loading...

, - ssr: false -}); - -export default function Sample() { - return Button; -} -``` - -:::warning -Don't forget to mark your component to 'use client'. -::: - -## 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](https://nextjs.org/docs/basic-features/environment-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: - -```javascript -MyApp.getInitialProps = async context => { - const URL = process.env.BASE_URL; - - return { - URL - }; -}; -``` - -:::tip -You'll need to set this `BASE_URL` variable inside the build process of whatever local build or CI/CD you have. This will need to be an absolute URL, as a relative URL will cause shoelace to throw a warning -::: - -## 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. - -```javascript -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; - } -}); -``` - -:::tip -This will copy the files from `node_modules` into your `static` folder on every development serve or build. You may want to avoid committing these into your repo. To do so, simply add `static/assets` into your `.gitignore` folder -::: - ## 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. +- 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.