add docs for nextjs 14 (#2096)

* add docs for nextjs 14

* prettier
pull/2104/head
Konnor Rogers 2024-07-10 11:20:37 -04:00 zatwierdzone przez GitHub
rodzic 0aecf69599
commit 325a9e2c4a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
1 zmienionych plików z 199 dodań i 166 usunięć

Wyświetl plik

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