TurborepoにBiomeを導入するための実践的な手順

## はじめに

- モダンなWeb開発において、コード品質の維持と開発効率の向上は重要な課題です。本記事では、Turborepoを使用したNext.jsプロジェクトに、高性能な開発ツールチェーンであるBiomeを導入する手順を解説します。

## Turborepoとは

- Turborepoは、JavaScript/TypeScriptプロジェクトのためのハイパフォーマンスなビルドシステムです。

### 主な特徴
- フレームワークに依存しない柔軟性（Next.js, React等で利用可能）
- インテリジェントなタスク実行とキャッシング
- モノレポ（monorepo）に最適化された設計

https://turbo.build/repo/docs

## Biomeとは

- Biomeは、Web開発のための次世代統合ツールチェーンです。JavaScript、TypeScript、JSX、JSON、CSS、GraphQLなどの幅広い言語をサポートし、コードの品質管理を効率化します。

### 主な特徴

#### 1. 高速なフォーマッタ機能
- コードの自動フォーマットを高速に実行
- Prettierと比較して数十倍の処理速度
- Prettierとの高い互換性により、スムーズな移行が可能

#### 2. 強力なリント機能
- 200以上の組み込みルール
- 詳細な診断結果の提供
- [豊富なルールセット](https://biomejs.dev/ja/linter/rules/)による包括的なコード分析

#### 3. 統合機能
- フォーマットとリントの同時実行をサポート
- 単一のツールでの効率的なコード品質管理
- シンプルな設定と運用

## 導入手順

### 1. Turborepoプロジェクトの作成

- まずは、Turborepoプロジェクトを作成します。

```bash:ターミナル
npx create-turbo@latest [プロジェクト名]
```

パッケージマネージャーは`pnpm`を選択しました。違うパッケージマネージャーを選択した場合は、コマンドを置き換えてから実行してください。

### 2. 既存の Linter/Formatter の削除

Biomeに移行するため、既存のPrettierとESLintの設定を削除します。

#### 2.1. package.json の修正

- 各ディレクトリの`package.json`から、Prettier・ESLint関連の設定を削除します。

```diff_json:package.json（/）

  "name": "tureborepo-next-biome-template",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
-   "lint": "turbo run lint",
-   "format": "prettier --write \"**/*.{ts,tsx,md}\"",
    "check-types": "turbo run check-types"
  },
  "devDependencies": {
-   "prettier": "^3.5.3",
    "turbo": "^2.4.4",
    "typescript": "5.8.2"
  },
  "packageManager": "pnpm@9.0.0",
  "engines": {
    "node": ">=18"
  }
}
```
```diff_json:turbo.json（/）
{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": [".next/**", "!.next/cache/**"]
    },
-   "lint": {
-     "dependsOn": ["^lint"]
-   },
    "check-types": {
      "dependsOn": ["^check-types"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
```
```diff_json:package.json（/apps/web）
{
  "name": "web",
  "version": "0.1.0",
  "type": "module",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack --port 3000",
    "build": "next build",
    "start": "next start",
-   "lint": "next lint --max-warnings 0",
    "check-types": "tsc --noEmit"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "next": "^15.2.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
-   "@repo/eslint-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.13.10",
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4",
-   "eslint": "^9.22.0",
    "typescript": "5.8.2"
  }
}
```
```diff_json:package.json（/apps/docs)
{
  "name": "docs",
  "version": "0.1.0",
  "type": "module",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack --port 3000",
    "build": "next build",
    "start": "next start",
-   "lint": "next lint --max-warnings 0",
    "check-types": "tsc --noEmit"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "next": "^15.2.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
-   "@repo/eslint-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.13.10",
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4",
-   "eslint": "^9.22.0",
    "typescript": "5.8.2"
  }
}
```
```diff_json:package.json（/packages/ui）
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "exports": {
    "./*": "./src/*.tsx"
  },
  "scripts": {
-   "lint": "eslint . --max-warnings 0",
    "generate:component": "turbo gen react-component",
    "check-types": "tsc --noEmit"
  },
  "devDependencies": {
-   "@repo/eslint-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@turbo/gen": "^2.4.4",
    "@types/node": "^22.13.10",
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4",
-   "eslint": "^9.22.0",
    "typescript": "5.8.2"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}
```

#### 2.2. 不要なファイルの削除

以下のファイルを削除します
- `packages/eslint-config/`（ディレクトリごと）
- `apps/web/eslint.config.mjs`
- `apps/docs/eslint.config.mjs`
- `packages/ui/eslint.config.mjs`

#### 2.3. 依存関係の更新
```bash:ターミナル
pnpm install
```

### 3. VSCode の設定

- Biomeをエディタに統合するため、`.vscode/settings.json`を作成または修正します。

```json:.vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "biomejs.biome",
  "editor.codeActionsOnSave": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  }
}
```

### 4. Biome の設定

#### 4.1. 設定パッケージの作成

- Biomeの設定を一元管理するための内部パッケージを作成します。
```bash:ターミナル
# 1. パッケージ作成 
mkdir -p packages/biome-config

# 2. パッケージ移動
cd packages/biome-config

# 3. パッケージ初期化
pnpm init
```

#### 4.2. Biome のインストールと初期化

```bash:ターミナル
# 1. インストール
pnpm add -D @biomejs/biome

# 2. 初期化
pnpm biome init
```

#### 4.3. 設定ファイルの変更

```json:package.json（/packages/biome-config）
{
  "name": "@repo/biome-config",
  "version": "1.0.0",
  "type": "module",
  "private": true,
  "exports": {
    "./biome": "./biome.json"
  },
  "devDependencies": {
    "@biomejs/biome": "^1.9.4"
  }
}
```
```json:biome.json（/packages/biome-config）
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "vcs": {
    "enabled": false,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "files": {
    "ignoreUnknown": false,
    "ignore": ["./tsconfig.json"]
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double"
    }
  }
}
```

### 5. ワークスペース（apps）と内部パッケージ（packages）にbiomeを導入する
#### 5.1 package.jsonの変更とbiome.jsonの追加
- 今回はプロジェクト作成時に生成される`apps/web`, `apps/docs`, `packages/ui`に導入します。

```diff_json:packages.json（apps/web）
{
  "name": "web",
  "version": "0.1.0",
  "type": "module",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack --port 3000",
    "build": "next build",
    "start": "next start",
    "check-types": "tsc --noEmit",
+   "biome:check": "biome check .",
+   "biome:fix": "biome check --fix ."
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "next": "^15.2.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
+   "@biomejs/biome": "^1.9.4",
+   "@repo/biome-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.13.10",
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4",
    "typescript": "5.8.2"
  }
}
```
```json:biome.json（apps/web）
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "extends": ["../../packages/biome-config/biome.json"]
}
```
```diff_json:packages.json（apps/docs）
{
  "name": "docs",
  "version": "0.1.0",
  "type": "module",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack --port 3000",
    "build": "next build",
    "start": "next start",
    "check-types": "tsc --noEmit",
+   "biome:check": "biome check .",
+   "biome:fix": "biome check --fix ."
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "next": "^15.2.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
+   "@biomejs/biome": "^1.9.4",
+   "@repo/biome-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.13.10",
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4",
    "typescript": "5.8.2"
  }
}
```
```json:biome.json（apps/docs）
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "extends": ["../../packages/biome-config/biome.json"]
}
```

```diff_json:packages.json（/packages/ui）
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "exports": {
    "./*": "./src/*.tsx"
  },
  "scripts": {
    "generate:component": "turbo gen react-component",
    "check-types": "tsc --noEmit",
+   "biome:check": "biome check .",
+   "biome:fix": "biome check --fix ."
  },
  "devDependencies": {
+   "@biomejs/biome": "^1.9.4",
+   "@repo/biome-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@turbo/gen": "^2.4.4",
    "@types/node": "^22.13.10",
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4",
    "typescript": "5.8.2"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}

```
```json:biome.json（/packages/ui）
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "extends": ["../biome-config/biome.json"]
}
```
:::note warn
注意
「extents」に関して、現状だと`@repo/biome-config/biome.json`のような指定方法は対応されてないため、相対パスで指定しています。
なので、`"@repo/biome-config": "workspace:*",`がなくてもbiomeは動作します。
（早く対応してほしいです…）
:::

#### 5.2 依存関係の更新
```bash:ターミナル（/）
# 1. ルートディレクトリに移動
cd ../..

# 2. インストール
pnpm install
```

### 6. turboの設定ファイル更新
- Biomeを使用している全ワークスペースおよび内部パッケージを一括で実行できるようにpakcage.jsonおよびturbo.jsonファイルを更新する。

```diff_json:package.json（/）
{
  "name": "tureborepo-next-biome-template",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "check-types": "turbo run check-types",
+   "biome:check": "turbo run biome:check",
+   "biome:fix": "turbo run biome:fix"
  },
  "devDependencies": {
    "turbo": "^2.4.4",
    "typescript": "5.8.2"
  },
  "packageManager": "pnpm@9.0.0",
  "engines": {
    "node": ">=18"
  }
}
```
```diff_json:turbo.json（/）
{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "check-types": {
      "dependsOn": ["^check-types"]
    },
+   "biome:check": {
+     "dependsOn": ["^biome:check"]
+   },
+   "biome:fix": {
+     "dependsOn": ["^biome:fix"]
+   },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

```

## 動作確認
#### 1. ファイル保存時にフォーマット整形およびインポートの並び替えがされるか
```diff_tsx:page.tsx（apps/web/app/page.tsx）
+ import { Button } from "@repo/ui/button";
  import Image, { type ImageProps } from "next/image";
- import { Button } from "@repo/ui/button";
  import styles from "./page.module.css";
```

#### 2. ルート配下で、`pnpm biome:check`を実行したときに正しくチェックされるか
```bash:コマンド
pnpm biome:check
# プロジェクト作成で生成されたファイルのままだと、ここでチェックエラーになります。
# 次の 'pnpm biome:fix' コマンドで解消することができます。
```

#### 3. ルート配下で、`pnpm biome:fix`を実行したときに正しく修正されるか
```bash:コマンド
pnpm run biome:fix
```
:::note warn
注意
packages/ui//button.tsxで「Provide an explicit type prop for the button element.」というエラーが発生した場合は、buttonタグにtype属性を追加することで`pnpm run biome:fix`が成功するようになります。
:::