【Next.js × PandaCSS】ゼロランタイムCSSで実現する高性能かつ快適な開発体験

## はじめに
いつもお世話になっております、Tonosakiと申します。
業務や個人開発など普段からNext.js(React)に触れておりますが、UIライブラリやCSSフレームワーク等のスタイリングの選択肢が多く、日々迷うことがあります。
私と同様に、スタイリングのベストプラクティスに悩んでいる方も多いかと思いますので、少しでも皆さんのお役に立てればと思い、今回は、最近注目しているPandaCSS について解説していこうと思います。ᕦ(ò_óˇ)ᕤ

## PandaCSSとは
PandaCSSとはChakra UIの開発元によって作られたゼロランタイムCSS in JSライブラリになります。[→公式ドキュメント](https://panda-css.com/)

#### CSS in JSとは何ぞや
CSS in JSとはJavaScriptのコード内にスタイルを書き込む手法で、外部のCSSファイルに依存することなく、コンポーネント単位でスタイリングをすることで動的なスタイリングを行います。

#### ゼロランタイムとは何ぞや
従来のランタイムなCSS in JSでは、コンポーネントがレンダリングされるたびにスタイルが再生成されるため、パフォーマンスの低下やオーバーヘッドが発生します。
一方、ゼロランタイムなCSS in JSでは、スタイルをビルド時に生成するため、レンダリングや実行時に不要なスタイルを生成することがありません。これにより、パフォーマンスの低下やオーバーヘッドが防がれ、ページの読み込みが速くなります。
また、不要なスタイルが生成されないため、最終的なCSSファイルのサイズも小さく抑えられます。

React Server ComponentsではランタイムなCSS in JSが非推奨になりつつあるので今までStyled ComponentsやEmotionなどのランタイムなCSS in JSしか使ったことない方はこれを機にPandaCSSを試してみるのも良いかもしれません。

## 導入
今回はNext.jsでの導入となります。VueやRemixなどの他のJSフレームワークに導入したい場合は以下参照下さい。
[→Next.js以外での導入はこちら](https://panda-css.com/docs/overview/getting-started#framework-guides)

### インストール、設定

<details><summary>pnpm</summary>

```
pnpm install -D @pandacss/dev
```
`postcss.config.js`ファイル作成
```
pnpm panda init --postcss
```

</details>

<details><summary>npm</summary>

```
npm install -D @pandacss/dev
```
`postcss.config.js`ファイル作成
```
npx panda init --postcss
```

</details>

<details><summary>yarn</summary>

```
yarn add -D @pandacss/dev
```
`postcss.config.js`ファイル作成
```
yarn panda init --postcss
```

</details>

<details><summary>bun</summary>

```
bun add -D @pandacss/dev
```
`postcss.config.js`ファイル作成
```
bun panda init --postcss
```

</details>

インストール後`package.json`ファイルに以下追加
```json:package.json
"scripts": {
  "prepare": "panda codegen",
}
```
Gitを使用している場合、自動生成された`styled-system`フォルダは管理対象外とするため`.gitignore`ファイルに以下を追加
```:.gitignore
styled-system
```
`src/app/globals.css`の全ての内容を以下コードに置き換え
```css:globals.css
@layer reset, base, tokens, recipes, utilities;
```
### 実装
PandaCSSの実装方法は従来のスタイリングとあまり変わりありません。
以下のように`css({})`でクラス内に直接スタイリングを記述することもできれば、あらかじめ変数に格納してクラスに指定する方法もあります。
```typescript:クラス内に直接記述
import { css } from '../../styled-system/css';

export default function Home() {
  return (
    <div
      className={css({
        fontSize: "10px",
        color: "black",
        padding: "10px 20px",
      })}
    >
      hello world!
    </div>
  );
}
```
```typescript:変数定義
import { css } from '../../styled-system/css';

export default function Home() {
  const style = css({
    fontSize: "10px",
    color: "black",
    padding: "10px 20px",
  });

  return (
    <div className={style}>
      hello world!
    </div>
  );
}
```


## レスポンシブ対応
レスポンシブ対応は以下のように`panda.config.ts`でブレークポイントを設定することで簡単に実装できます。
```typescript:panda.config.ts
export const definePandaConfig({
  theme: {
    breakpoints: {
      sm: '640px', // スマートフォン向け
      md: '768px', // タブレット向け
      lg: '1024px', // デスクトップ向け
      xl: '1280px', // 大きいデスクトップ向け
    },
  },
})
```
設定したブレークポイントでスタイリングを指定する場合は以下のように記述します。
```typescript:page.tsx
export default function Home() {
  return (
    <div
      className={css({
        margin: "20px",
        fontSize: "30px",
        sm: {
          margin: "5px",
          fontSize: "10px",
        },
      })}
    >
      hello world!
    </div>
  );
}
                    
```

## 疑似要素
ホバーなどで使う疑似要素に関しては以下のように記述します。
```typescript:page.tsx
export default function Home() {
  return (
    <button
      type="button"
      className={css({
        bg: "black",
        color: "white",
        _hover: {
          bg: "gray",
          color: "black",
      })}
      onClick={}
    >
      Click!
    </button>        
  );
}
```
`_hover`の他にも`_focus`や`_before`も同じ形式で記述できます。

## レシピ
レシピはスタイリングのパターンや構造を再利用可能な形で定義し、動的なスタイルや状態に応じたスタイリングの再利用を促進するために使用されます。

レシピは以下の4つのプロパティから構成されます。
- `base`：基本スタイル
- `variants`：スタイルを異なるバリエーションに分けるために使う
- `compoundVariants`：複数のバリアントを組み合わせて、特定の条件下でのみ適用されるスタイルを定義するために使用
- `defaultVariants`：初期状態として適用されるバリアント値を指定するために使用

以下ではレシピの定義し、コンポーネントでプロパティを渡してスタイリングをしています。
`({ color: 'cream', size: 'lg' })`の組み合わせに応じてスタイルを動的に生成しています。
`compoundVariants`を使うことで特定の組み合わせに対して追加のスタイルを適用させます。
`defaultVariants`でデフォルトのスタイル設定を定義できます。
```typescript:page.tsx
import { cva } from '../styled-system/css'

const button = cva({
  base: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: '4px',
    fontWeight: 'bold',
  },
  variants: {
    color: {
      cream: { bg: '#FFE4DB', color: '#DF4D20' },
      orange: { bg: '#DF4D20', color: '#ffffff' },
    },
    size: {
      lg: { fontSize: '24px', padding: '8px' },
      sm: { fontSize: '12px', padding: '4px' },
    },
  },
  // 複数のバリアントの組み合わせによるスタイルの適用
  compoundVariants: [
    {
      color: 'orange',
      size: 'lg',
      css: {
        borderRadius: '8px',
      },
    },
  ],
  // デフォルトのバリアント設定
  defaultVariants: {
    color: 'cream',
    size: 'sm',
  },
});
 
export default function Button() {
  return (
    <button className={button({ color: 'cream', size: 'lg' })}>
      Click!
    </button>
  )
}
```

## ※注意点
Next.jsでは、PandaCSSで生成されたスタイルがキャッシュされることがあります。その際は、以下のコマンドで`.next`フォルダを削除し、開発サーバーを再起動することで解決できます。
```
rm -rf .next
npm run dev
```
また、`package.json`に以下記述で各ビルド前に`.next`フォルダを削除することもできます。
```diff_json:package.json
"scripts": {
-  "dev": "next dev",
+  "dev": "rm -rf .next && next dev",
}
```
## 最後に
ここまで読んでいただきありがとうございます( っ- ‸ -c)
PandaCSSはここ1、2年で開発された比較的新しい技術なので、初めて耳にする方も多いかと思います。個人的にはTailwindCSSと同じくらい、開発体験が優れており、パフォーマンスにも申し分ないと感じています。

ここ数年のweb業界は技術の進歩や、入れ替わりが激しく、様々なフレームワークやライブラリであふれかえってます。
時には流行に乗り遅れた気がして焦ることもありますが、その分、開発体験を大きく向上させる技術も増えています。生成AIの助けを借りることで、キャッチアップも早くなり、開発の効率も格段にアップしました。
これからもどんどん進化する技術を楽しみながら、より良いアプリケーションを作っていきたいと思います！