# Next.js

> 在 Next.js 的 App Router 或 Pages Router 中接入 React 颜色选择器，含样式表导入、use client 规则和页面示例。

Source: https://chroma-panel.jscrate.dev/zh/react/frameworks/next-js
Last updated: 2026-09-21

安装 chroma-panel，导入一次样式表，就可以在 Next.js 的任意一种路由中渲染它。服务端组件可以直接导入选择器。只有当你自己的组件用到状态或传入事件处理函数时，才需要加 `"use client"`。

## 安装

```bash
npm install chroma-panel
```

不需要再装其他包。`dependencies` 是空的，`react` 和 `react-dom` 是 peer 依赖，所以用的就是你应用里已有的那一份。

## 样式表

面板在 effect 中注入自己的样式表。这意味着在水合完成之前，服务端渲染出的标记是没有样式的。要避免这种闪烁，可以自己导入样式表，并关闭自动注入：

```tsx
<ColorInput injectStyles={false} />
```

导入写在哪里取决于你用的路由，下面各节分别给出了写法。`injectStyles={false}` 只是不再写入 `<style>` 标签，无论哪种方式，CSS 都会进入你的包。如果你使用 Tailwind，请改为在 CSS 文件中导入样式表，并声明层级顺序，具体见[使用 Tailwind 样式](https://chroma-panel.jscrate.dev/zh/react/handbook/tailwind)。

## 配置页面

### App Router

在根布局中导入一次样式表。布局仍然是服务端组件。

```tsx
// app/layout.tsx
import "chroma-panel/style.css";

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

只要传入的每个 prop 都可以序列化，服务端组件就能直接渲染选择器：

```tsx
// app/brand/page.tsx
import { ColorInput } from "chroma-panel";

export default function BrandPage() {
  return (
    <main>
      <h2>Brand color</h2>
      <ColorInput defaultValue="#3366cc" injectStyles={false} />
    </main>
  );
}
```

一旦由你自己保存颜色，这个文件就得是客户端组件。`useState` 和 `onChange` 处理函数都只能在客户端使用，所以要把它们放在你自己的 `"use client"` 文件里：

```tsx
// app/brand/brand-picker.tsx
"use client";

import { useState } from "react";
import { ColorInput, type ColorChangeResult } from "chroma-panel";

export function BrandPicker() {
  const [color, setColor] = useState<string>("#3366cc");

  const handleChange = (result: ColorChangeResult): void => {
    setColor(result.hex);
  };

  return (
    <>
      <ColorInput value={color} onChange={handleChange} injectStyles={false} />
      <p style={{ color }}>{color}</p>
    </>
  );
}
```

这个文件之所以是客户端组件，是因为你的状态，而不是因为 chroma-panel。哪些 prop 由你管理、哪些由面板自己保存，见[受控与非受控](https://chroma-panel.jscrate.dev/zh/react/handbook/controlled)。

### Pages Router

Pages Router 没有服务端组件，所以任何地方都用不到 `"use client"`。Next.js 只允许在 `pages/_app.tsx` 中导入全局样式表，CSS 就放在这里：

```tsx
// pages/_app.tsx
import type { AppProps } from "next/app";

import "chroma-panel/style.css";

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
```

页面本身就是一个普通的 React 组件：

```tsx
// pages/brand.tsx
import { useState } from "react";
import { ColorInput, type ColorChangeResult } from "chroma-panel";

export default function BrandPage() {
  const [color, setColor] = useState<string>("#3366cc");

  const handleChange = (result: ColorChangeResult): void => {
    setColor(result.hex);
  };

  return (
    <main>
      <ColorInput value={color} onChange={handleChange} injectStyles={false} />
    </main>
  );
}
```

## 哪里需要 "use client"

| 文件                         | 是否需要 `"use client"` |
| ---------------------------- | ----------------------- |
| 渲染选择器的服务端组件       | 否                      |
| 用 `useState` 保存颜色的文件 | 是                      |
| 传入 `onChange` 的文件       | 是                      |
| `pages/` 下的任何文件        | 不适用                  |

这条规则针对的是你自己的代码。服务端组件无法跨越边界传递函数，所以只要有处理函数，就必须是客户端组件。

## 无需动态导入

你不需要 `next/dynamic`，也不需要 `ssr: false`。这个包在模块加载时不会读取 `window`。在服务端会跳过样式注入，但选中的颜色依然会以 CSS 自定义属性的形式写入标记。

> **你不需要 ssr: false**
>
> 用 `ssr: false`
> 的动态导入包裹选择器，只会让它晚一个往返才出现。它原样就能在服务端正确渲染。

[服务端渲染](https://chroma-panel.jscrate.dev/zh/react/handbook/server-rendering)针对 Remix 和其他 SSR 方案讲了同样的内容；如果你想要比默认导入更小的包，[入口](https://chroma-panel.jscrate.dev/zh/react/utils/entry-points)列出了所有子路径。
