---
title: "labee.devは48時間で段階移行し、labee.jpは一括でAstroへ移した"
description: "ラビー合同会社のコーポレートサイト（labee.jp）と開発者ツールサイト（labee.dev）をNext.jsからAstroへ移行しました。labee.devは48時間で3段階の段階移行、labee.jpはその経験を踏まえた一括移行です。Tailwind CSSの廃止とCSSカスタムプロパティへの移行についても紹介します。"
category: "Web"
tags: ["Astro","Next.js","Cloudflare Workers","CSS","Tailwind CSS","マイグレーション"]
publishedAt: "2026-04-15"
lastmod: "2026-08-14"
---

[Cloudflare Workers](https://developers.cloudflare.com/workers/)上で動くコンテンツ中心のサイトに、**[React](https://react.dev/)のランタイムは不要だった**ため、ラビー合同会社のコーポレートサイト（labee.jp）と開発者ツールサイト（labee.dev）を[Next.js](https://nextjs.org/)から[Astro](https://astro.build/)へ移行しました。Astroの [Islands Architecture](https://docs.astro.build/en/concepts/islands/) により、インタラクティブな部分だけにJavaScriptを限定し、それ以外は静的HTMLとして配信できます。さらに、[Content Collection](https://docs.astro.build/en/guides/content-collections/)によるfrontmatterのスキーマバリデーションや[remark](https://github.com/remarkjs/remark)プラグインの柔軟な拡張など、Markdownベースのコンテンツ管理基盤としてもAstroが適していました。labee.devは**48時間**で3段階の段階移行、labee.jpはその経験を踏まえた一括移行です。

::card[https://labee.jp]

::card[https://labee.dev]

## Next.jsで困っていたこと

2つのサイトはどちらもCloudflare Workersで動作するコンテンツ中心のサイトです。Next.jsを使っていましたが、フレームワークの方向性とサイトの性質にズレを感じていました。

labee.jpはコーポレートサイトで、インタラクティブなUIはヘッダーのモバイルメニュー程度です。Reactは過剰でした。labee.devはDNS RecordsチェッカーなどのReactコンポーネントを持ちますが、それ以外のページ（ブログ、用語集、ガイド）は静的コンテンツです。

Cloudflare Workers環境固有の制約もありました。Next.jsはVercelでの運用を前提に最適化されており、**Workers上ではNode.js互換レイヤーに依存する**場面が多くなります。[`@opennextjs/cloudflare`](https://opennext.js.org/cloudflare)のようなコミュニティアダプターを介してデプロイすることは可能ですが、App Routerの一部機能には、Workers環境で追加の設定やワークアラウンドが必要でした。`@opennextjs/cloudflare` を挟む必要があり、Next.jsのアップデートへの追従にもラグが生じます。ビルドサイズもWorkerの容量制限を意識する必要があり、React Server Componentsのバンドルが積み重なると無視できません。コンテンツサイトに対してこれらの制約を回避し続けるコストは、フレームワークを変える判断を後押ししました。

ブログ記事の管理にはAstroのContent Collectionが使いたい機能でした。frontmatterの[Zod](https://zod.dev/)バリデーション、globローダーによるファイルベース管理、remarkプラグインの柔軟な拡張。noteで運用していたブログを自社サイトに統合するにあたり、Markdownの処理基盤としてAstroが適していました。

## labee.dev は48時間で段階移行した

labee.devは3つのステップで段階的に移行しました。

### ステップ1: AstroとSEO最適化の導入

既存のNext.jsプロジェクトにAstroを並行導入しました。SEO最適化が主目的で、ブログやガイドなどの静的コンテンツの生成をAstroに任せる構成を検証しています。この段階ではNext.jsのルーティングとAstroのルーティングが共存しており、Astroが担当するパスだけを切り出す形です。既存のReactコンポーネントやページには手を入れず、**影響範囲を新規の静的ページに限定する**ことでリスクを最小化しました。

### ステップ2: 全ページのSSR化

すべてのページの静的部分をAstroに移行し、SSR構成に切り替えました。Reactコンポーネントは [`@astrojs/react`](https://docs.astro.build/en/guides/integrations-guide/react/) インテグレーションを通じてAstroページ内に埋め込んでいます。具体的には、DNS Recordsチェッカーのような対話的なツール画面のコンポーネントを `.tsx` のまま残し、Astroテンプレートから `client:load` ディレクティブで読み込む形です。ページのレイアウト（ヘッダー、フッター、サイドバー）はAstroコンポーネントに書き換え、Reactが担う範囲をツールのインタラクティブ部分だけに限定しました。Astroへの移行はReactを捨てることではなく、**Reactが必要な部分だけにReactを使う構成への変更**です。

### ステップ3: ファイル構成の整理

Astroのプラクティスに合わせてファイル構成を整理しました。Next.jsの `pages/` ディレクトリ構造からAstroの `src/pages/` 構造への再配置、不要になったクライアントコンポーネント（`App.tsx` やルーティング関連のラッパー）の削除、CSS管理の統合などを行っています。この段階で `next.config.js` や `_app.tsx`、`_document.tsx` といったNext.js固有のファイルもすべて削除し、依存パッケージからNext.jsとReact DOMを除去しました。

**48時間**で3段階に分けたのは、各段階でビルドとデプロイを確認し、問題があればすぐに切り戻せるようにするためです。

## labee.jp は一括で移行した

labee.jpは一括移行しました。

約5,000行の追加と約5,700行の削除で、差し引き**約500行**のコード削減です。

labee.devでの段階移行の経験があったため、一括でも問題なく進められました。モノレポ構成（`apps/web/`）を廃止してルート直下のシンプルな構成に変更し、[ESLint](https://eslint.org/) + [Prettier](https://prettier.io/)を[Biome](https://biomejs.dev/)に置き換えています。

インタラクティブなUIがほぼないため、Reactは導入せずすべてのコンポーネントをpure Astroで実装しました。ヘッダーのモバイルメニューはvanilla JSで十分です。スクロールアニメーションもCSSトランジションと[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)の組み合わせで実装しており、[framer-motion](https://motion.dev/)のようなライブラリは不要でした。

## [Tailwind CSS](https://tailwindcss.com/)の廃止

両サイトとも、移行に合わせてTailwind CSSを廃止しました。

Tailwindのユーティリティクラスはプロトタイピングに有効ですが、コンポーネント数が限られたコンテンツサイトではBEM的なCSS設計の方が見通しがよくなります。サイトのデザインが固まったタイミングで、CSSカスタムプロパティによるデザイントークンシステムに移行しました。

Tailwind CSS v4はCSS-firstのアプローチを採用しており、`tailwind.config.ts` の代わりにCSS内の `@theme` ディレクティブでデザイントークンを定義します。従来のJavaScript設定ファイルが不要になり、CSSだけで設定が完結する点は大きな改善です。しかし、`@theme` で定義したトークンは**Tailwindのユーティリティクラスを通じて参照する前提**であり、カスタムプロパティを直接書くのとは設計思想が異なります。コンテンツサイトではユーティリティクラスの恩恵が薄く、CSSカスタムプロパティを直接定義して直接参照する方がシンプルでした。Tailwind v4のCSS-firstな考え方に触れたことで、逆に「それなら素のCSSカスタムプロパティで十分だ」と判断できた面もあります。

```css
/* src/styles/tokens.css（一部抜粋）*/
:root {
  --color-primary-50: #fffdf0;
  --color-primary-500: #ffb700;
  --color-primary-700: #b36b00;

  --space-1: 4px;
  --space-2: 8px;
  --space-4: 16px;

  --text-sm: 13px;
  --text-base: 15px;

  --layout-max: 1120px;
}
```

CSSファイルはモジュール化して管理しています。

```text
src/styles/
  globals.css      # @importの集約
  reset.css        # CSSリセット
  tokens.css       # デザイントークン
  base.css         # HTML要素のデフォルト
  layout.css       # ヘッダー・フッター
  components.css   # 共通コンポーネント
  content.css      # 記事コンテンツ
  utilities.css    # ユーティリティ
```

Tailwindの `@apply` は不要になり、CSSの可読性が向上しました。デザイントークンは1ファイルに集約されているため、グローバルなスタイル変更も容易です。

## Cloudflare Workersへのデプロイ

[`@astrojs/cloudflare`](https://docs.astro.build/en/guides/integrations-guide/cloudflare/) アダプターを使い、`output: 'server'` でSSRを有効にしています。

```typescript
// astro.config.mjs（簡略化）
export default defineConfig({
  site: 'https://labee.jp',
  output: 'server',
  adapter: cloudflare({ prerenderEnvironment: 'node' }),
})
```

`prerenderEnvironment: 'node'` は、プリレンダリング対象ページのビルドにNode.js環境を使う設定です。Content Collectionの処理などNode.js APIに依存する処理が正常に動作します。

`wrangler.jsonc` の `run_worker_first: true` により、リクエストはまずWorkerで処理されます。プリレンダリング済みページは静的HTMLとして配信され、動的ページはWorkerがSSRで生成します。

## CIの型チェック

移行後のCIで `tsc --noEmit` が失敗する問題がありました。`astro:content` モジュールの型定義が `.astro/types.d.ts` に生成されるのですが、CIのクリーンな環境ではこのファイルが存在しません。

[Astro公式ドキュメント](https://docs.astro.build/en/reference/cli-reference/#astro-check)によると、`astro check` コマンドは内部で `astro sync` を実行し、型定義ファイルを生成します。`tsc` の前に `astro check` を実行する構成に変更しました。

```json
{
  "typecheck": "astro check && tsc --noEmit"
}
```

labee.devではこの問題が表面化していなかったのは、`.astro/` ディレクトリ（`types.d.ts` を含む）がリポジトリにコミットされていたためです。ローカルで `astro dev` や `astro build` を実行すると `.astro/types.d.ts` が自動生成され、それがそのままコミットに含まれていました。**CIでもこのコミット済みの型定義を参照していたため、`astro sync` なしでも `tsc` が通っていた**のです。しかし、公式ドキュメントでは `.astro/` は `.gitignore` に含めるのが推奨されています。生成物をバージョン管理に含めると、開発者間でAstroのバージョン差異がある場合に型定義の不整合が発生するリスクがあります。labee.jpでは最初から `.astro/` を `.gitignore` に入れていたため問題が顕在化し、`astro check` を使う正しい構成を採用できました。labee.devも同じ構成に修正しています。

## 移行後に広がったこと

Astroへの移行後、remarkプラグインを自由に追加できるようになりました。この記事を書いている時点で、リンクカード、用語自動リンク、Mermaidダイアグラムの3つのプラグインが稼働しています。

::card[/posts/astro-link-card]

::card[/posts/astro-glossary-autolink]

::card[/posts/astro-mermaid]

noteで運用していたブログも自社サイトに統合し、過去の記事をMarkdownに変換して移行しました。Content CollectionのZodスキーマによるfrontmatterバリデーション、ビルド時の型チェック、CIでの整合性検証。Astroのエコシステムが、コンテンツ管理の基盤として機能しています。