kopia lustrzana https://github.com/shoelace-style/shoelace
rodzic
0aecf69599
commit
325a9e2c4a
|
@ -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: <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
|
||||
|
@ -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 && <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'
|
||||
|
||||
```javascript
|
||||
'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>;
|
||||
}
|
||||
```
|
||||
|
||||
:::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.
|
||||
|
|
Ładowanie…
Reference in New Issue