T-CREATOR

Remix × Vite 構成の作り方:開発サーバ・ビルド・エイリアス設定【完全ガイド】

Remix × Vite 構成の作り方:開発サーバ・ビルド・エイリアス設定【完全ガイド】

Remix の開発体験が大きく進化しました。従来の Remix Compiler から、高速で柔軟な Vite へと移行できるようになったのです。 本記事では、Remix × Vite 構成の構築方法を、開発サーバの起動からビルド設定、パスエイリアスの活用まで、実践的な手順とともに解説いたします。

初めて Vite を導入する方でも、ステップバイステップで理解できる内容になっていますので、ぜひ最後までお読みください。

背景

Remix の開発環境の変遷

Remix は当初、独自のコンパイラ(@remix-run​/​dev)を使用していました。 しかし、フロントエンド開発ツールのエコシステムが進化する中で、より高速で拡張性の高いビルドツールへの需要が高まってきたのです。

そこで登場したのが、Vite との統合です。Vite は次世代のフロントエンドビルドツールとして、以下の特徴を持っています。

Vite が選ばれる理由

Remix チームが正式に Vite をサポートすることを決定した背景には、いくつかの明確な理由があります。

以下の図は、従来の Remix Compiler と Vite 構成の違いを示しています。

mermaidflowchart TB
  subgraph old["従来のRemix Compiler"]
    oldDev["remix dev"] --> oldBuild["esbuild による<br/>バンドル"]
    oldBuild --> oldOutput["ビルド出力"]
  end

  subgraph new["Vite構成"]
    newDev["vite dev"] --> viteHMR["高速HMR<br/>(ミリ秒単位)"]
    newBuild["vite build"] --> viteBuild["Rollup最適化<br/>バンドル"]
    viteHMR --> newOutput["開発サーバ"]
    viteBuild --> prodOutput["本番ビルド"]
  end

この図から分かるように、Vite 構成では開発とビルドの両面で最適化されています。

主な利点:

#項目従来の Remix CompilerVite 構成
1開発サーバ起動速度数秒〜数十秒ミリ秒単位 ★★★
2HMR(Hot Module Replacement)あり高速・安定 ★★★
3プラグインエコシステム限定的豊富(1000+) ★★★
4ビルド最適化esbuildRollup(細かい制御可能) ★★
5TypeScript 対応要設定ネイティブサポート ★★★

課題

移行時に直面する問題

Remix × Vite 構成への移行は魅力的ですが、いくつかの課題に直面することがあります。

1. 設定ファイルの違い

従来のremix.config.jsからvite.config.tsへの移行が必要です。 設定項目の名称や構造が異なるため、既存プロジェクトの移行時には注意が必要でしょう。

2. プラグインの互換性

Remix の機能(ファイルベースルーティング、ローダー、アクションなど)を Vite 上で動作させるには、専用のプラグイン(@remix-run​/​devの Vite プラグイン)が必要になります。

3. パスエイリアスの設定

開発時に便利な@​/​componentsのようなパスエイリアスを使うには、Vite と TypeScript の両方で設定が必要です。 片方だけでは正しく動作しないため、設定漏れが発生しやすい問題があります。

4. ビルド出力の違い

Vite のビルド出力構造は、従来の Remix Compiler と異なります。 デプロイ設定や環境変数の読み込み方法も調整が必要でしょう。

以下の図は、パスエイリアスの解決フローを示しています。

mermaidflowchart LR
  code["コード内<br/>import from '@/utils'"] --> tsconfig["tsconfig.json<br/>paths設定"]
  code --> viteConfig["vite.config.ts<br/>resolve.alias設定"]

  tsconfig --> tsc["TypeScript<br/>型チェックOK"]
  viteConfig --> viteDev["Vite開発サーバ<br/>モジュール解決OK"]
  viteConfig --> viteBuild["Viteビルド<br/>バンドルOK"]

  tsc --> success["正常動作"]
  viteDev --> success
  viteBuild --> success

両方の設定が揃って初めて、パスエイリアスが完全に機能します。

解決策

Remix × Vite 構成の導入手順

ここからは、実際に Remix × Vite 構成を構築する具体的な手順を解説します。 段階的に進めることで、確実にセットアップできるでしょう。

ステップ 1:新規プロジェクトの作成

まず、Vite テンプレートを使って Remix プロジェクトを作成します。

bash# Remixプロジェクトを作成(Viteテンプレート使用)
npx create-remix@latest my-remix-app --template remix-run/remix/templates/vite
bash# プロジェクトディレクトリに移動
cd my-remix-app
bash# 依存関係をインストール
yarn install

これで、Vite 構成の Remix プロジェクトが作成されました。 テンプレートには必要な設定があらかじめ含まれているため、すぐに開発を始められます。

ステップ 2:Vite 設定ファイルの理解

プロジェクトルートに生成されるvite.config.tsが、Vite 構成の中心となります。 このファイルで、開発サーバ、ビルド、プラグインなどを制御します。

基本的な vite.config.ts の構造

typescriptimport { vitePlugin as remix } from '@remix-run/dev';
import { defineConfig } from 'vite';

上記は、必要なモジュールのインポートです。 @remix-run​/​devから Remix 用の Vite プラグインを読み込んでいます。

typescriptexport default defineConfig({
  plugins: [
    remix({
      // Remix固有の設定
      ignoredRouteFiles: ['**/*.css'],
    }),
  ],
});

plugins配列に Remix プラグインを追加することで、Remix の機能が Vite 上で動作します。 ignoredRouteFilesでは、ルートとして認識しないファイルパターンを指定できますね。

ステップ 3:開発サーバの設定

開発体験を向上させるため、開発サーバのカスタマイズを行います。

typescriptexport default defineConfig({
  plugins: [remix()],
  server: {
    port: 3000,
    host: true,
    open: true,
  },
});

設定項目の説明:

#設定項目説明デフォルト値
1port開発サーバのポート番号5173
2hostネットワークからのアクセス許可(true: 0.0.0.0)localhost
3openサーバ起動時にブラウザを自動で開くfalse

この設定により、http:​/​​/​localhost:3000で開発サーバが起動し、自動でブラウザが開きます。

HMR(Hot Module Replacement)の設定

Vite の HMR は標準で有効ですが、細かい調整も可能です。

typescriptexport default defineConfig({
  plugins: [remix()],
  server: {
    port: 3000,
    hmr: {
      overlay: true,
      protocol: 'ws',
    },
  },
});

overlay: trueにより、エラーが発生した際にブラウザ上にオーバーレイで表示されます。 開発中のデバッグが格段に楽になるでしょう。

ステップ 4:ビルド設定の最適化

本番環境向けのビルド設定を行います。

typescriptexport default defineConfig({
  plugins: [remix()],
  build: {
    target: 'esnext',
    minify: 'esbuild',
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: undefined,
      },
    },
  },
});

ビルド設定の各項目:

  • target: 出力する JavaScript のバージョン(esnextで最新仕様を利用)
  • minify: コード圧縮方法(esbuildは高速、terserはより小さいサイズ)
  • sourcemap: ソースマップの生成(本番環境では false を推奨)
  • rollupOptions: Rollup の詳細設定

チャンク分割の戦略

大規模アプリケーションでは、適切なチャンク分割が重要です。

typescriptexport default defineConfig({
  plugins: [remix()],
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // node_modules配下を別チャンクに
          if (id.includes('node_modules')) {
            return 'vendor';
          }
        },
      },
    },
  },
});

この設定により、外部ライブラリがvendor.jsとして分離されます。 アプリケーションコードの更新時に、ユーザーはライブラリ部分を再ダウンロードする必要がなくなりますね。

ステップ 5:パスエイリアスの設定

コードの可読性と保守性を高めるため、パスエイリアスを設定します。 ..​/​..​/​..​/​components​/​Buttonのような相対パスを、@​/​components​/​Buttonのようにシンプルに書けるようになります。

vite.config.ts でのエイリアス設定

typescriptimport { vitePlugin as remix } from '@remix-run/dev';
import { defineConfig } from 'vite';
import path from 'path';

まず、Node.js のpathモジュールをインポートします。 これを使って、絶対パスを生成します。

typescriptexport default defineConfig({
  plugins: [remix()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './app'),
      '~': path.resolve(__dirname, './app'),
    },
  },
});

@~の両方をappディレクトリに紐付けています。 どちらも同じ意味ですが、プロジェクトの慣習に合わせて選べますね。

tsconfig.json でのパス設定

TypeScript の型チェックを正しく機能させるため、tsconfig.jsonにも同じパス設定が必要です。

json{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./app/*"],
      "~/*": ["./app/*"]
    }
  }
}

baseUrlを設定することで、pathsの起点が定まります。 これにより、TypeScript がインポート文を正しく解釈できるようになるでしょう。

より詳細なエイリアス設定

実際のプロジェクトでは、複数のエイリアスを使い分けると便利です。

typescriptexport default defineConfig({
  plugins: [remix()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './app'),
      '@components': path.resolve(
        __dirname,
        './app/components'
      ),
      '@utils': path.resolve(__dirname, './app/utils'),
      '@hooks': path.resolve(__dirname, './app/hooks'),
      '@styles': path.resolve(__dirname, './app/styles'),
      '@types': path.resolve(__dirname, './app/types'),
    },
  },
});

用途別にエイリアスを分けることで、インポート文がより意味的になります。

対応するtsconfig.jsonの設定は以下の通りです。

json{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./app/*"],
      "@components/*": ["./app/components/*"],
      "@utils/*": ["./app/utils/*"],
      "@hooks/*": ["./app/hooks/*"],
      "@styles/*": ["./app/styles/*"],
      "@types/*": ["./app/types/*"]
    }
  }
}

これで、import Button from "@components​/​Button"のように、明確なインポートが可能になりました。

ステップ 6:環境変数の設定

Vite では、環境変数の扱い方が独特です。 正しく設定することで、開発・本番環境での値の切り替えが簡単になります。

.env ファイルの作成

プロジェクトルートに.envファイルを作成します。

bash# .env
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My Remix App

重要な注意点: Vite では、クライアント側で使用する環境変数にはVITE_プレフィックスが必要です。 このプレフィックスがない変数は、ビルド時にバンドルされません。

環境変数の読み込み方法

Remix コンポーネント内での環境変数の使用例です。

typescriptexport default function Index() {
  // Viteの環境変数にアクセス
  const apiUrl = import.meta.env.VITE_API_URL;
  const appTitle = import.meta.env.VITE_APP_TITLE;

  return (
    <div>
      <h1>{appTitle}</h1>
      <p>API URL: {apiUrl}</p>
    </div>
  );
}

import.meta.env経由で環境変数にアクセスします。 これは、Vite の標準的な方法ですね。

TypeScript の型定義

環境変数に型をつけることで、より安全に使用できます。

typescript// app/types/env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string;
  readonly VITE_APP_TITLE: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

この型定義により、環境変数のオートコンプリートと型チェックが有効になります。

具体例

実践的なプロジェクト構成

ここでは、実際のプロジェクトで使用できる完全な設定例を紹介します。 この構成は、中〜大規模の Remix アプリケーションに適しています。

完全な vite.config.ts

以下は、これまで解説した内容を統合した、本番環境対応の設定ファイルです。

typescriptimport { vitePlugin as remix } from '@remix-run/dev';
import { defineConfig } from 'vite';
import path from 'path';
typescriptexport default defineConfig({
  // Remixプラグインの設定
  plugins: [
    remix({
      ignoredRouteFiles: ["**/*.css", "**/*.test.{ts,tsx}"],
      serverModuleFormat: "esm",
    }),
  ],

serverModuleFormat: "esm"により、サーバー側も ES モジュールとして出力されます。 これは、Node.js の最新機能を活用できるメリットがあります。

typescript  // 開発サーバの設定
  server: {
    port: 3000,
    host: true,
    open: true,
    hmr: {
      overlay: true,
    },
  },
typescript  // ビルド設定
  build: {
    target: "esnext",
    minify: "esbuild",
    sourcemap: process.env.NODE_ENV === "development",
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes("node_modules")) {
            if (id.includes("react") || id.includes("react-dom")) {
              return "react-vendor";
            }
            return "vendor";
          }
        },
      },
    },
  },

React 関連ライブラリを別チャンクに分離することで、キャッシュ効率が向上します。

typescript  // パスエイリアスの設定
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./app"),
      "@components": path.resolve(__dirname, "./app/components"),
      "@utils": path.resolve(__dirname, "./app/utils"),
      "@hooks": path.resolve(__dirname, "./app/hooks"),
      "@styles": path.resolve(__dirname, "./app/styles"),
      "@types": path.resolve(__dirname, "./app/types"),
      "@lib": path.resolve(__dirname, "./app/lib"),
    },
  },
typescript  // CSS設定
  css: {
    modules: {
      localsConvention: "camelCase",
    },
  },
});

CSS Modules を使用する場合、localsConventionにより、クラス名のケースを自動変換できます。

完全な tsconfig.json

TypeScript の設定も、Vite と連携させます。

json{
  "compilerOptions": {
    "lib": ["DOM", "DOM.Iterable", "ES2022"],
    "types": ["@remix-run/node", "vite/client"],
    "isolatedModules": true,
    "esModuleInterop": true,
    "jsx": "react-jsx",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "target": "ES2022",
    "strict": true,
    "allowJs": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
json    "baseUrl": ".",
    "paths": {
      "@/*": ["./app/*"],
      "@components/*": ["./app/components/*"],
      "@utils/*": ["./app/utils/*"],
      "@hooks/*": ["./app/hooks/*"],
      "@styles/*": ["./app/styles/*"],
      "@types/*": ["./app/types/*"],
      "@lib/*": ["./app/lib/*"]
    },
json    "noEmit": true
  },
  "include": ["**/*.ts", "**/*.tsx", "**/.server/**/*.ts", "**/.server/**/*.tsx", "**/.client/**/*.ts", "**/.client/**/*.tsx"]
}

"noEmit": trueにより、TypeScript はトランスパイルせず、型チェックのみ行います。 実際のトランスパイルは Vite が担当しますね。

ディレクトリ構造の例

パスエイリアスを活用した、推奨ディレクトリ構造です。

rubymy-remix-app/
├── app/
│   ├── components/          # @components
│   │   ├── ui/
│   │   │   ├── Button.tsx
│   │   │   └── Input.tsx
│   │   └── layout/
│   │       ├── Header.tsx
│   │       └── Footer.tsx
│   ├── hooks/              # @hooks
│   │   ├── useAuth.ts
│   │   └── useFetch.ts
│   ├── lib/                # @lib
│   │   ├── api.ts
│   │   └── constants.ts
│   ├── routes/
│   │   ├── _index.tsx
│   │   └── about.tsx
│   ├── styles/             # @styles
│   │   ├── global.css
│   │   └── theme.css
│   ├── types/              # @types
│   │   ├── env.d.ts
│   │   └── user.ts
│   └── utils/              # @utils
│       ├── format.ts
│       └── validate.ts
├── public/
├── .env
├── package.json
├── tsconfig.json
└── vite.config.ts

実際の使用例

パスエイリアスを使った実際のコード例を見てみましょう。

コンポーネントでの使用

typescript// app/routes/_index.tsx
import { json } from '@remix-run/node';
import type { LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';

// パスエイリアスを使ったインポート
import { Button } from '@components/ui/Button';
import { Header } from '@components/layout/Header';
import { useAuth } from '@hooks/useAuth';
import { formatDate } from '@utils/format';
import type { User } from '@types/user';

相対パスを使わないため、ファイルの移動時にインポート文を修正する必要がありません。

typescriptexport async function loader({
  request,
}: LoaderFunctionArgs) {
  const apiUrl = process.env.VITE_API_URL;
  // データ取得ロジック
  return json({ users: [] });
}
typescriptexport default function Index() {
  const { users } = useLoaderData<typeof loader>();
  const { isAuthenticated } = useAuth();

  return (
    <div>
      <Header />
      <main>
        <h1>Welcome to Remix with Vite</h1>
        {isAuthenticated && (
          <Button variant='primary'>Get Started</Button>
        )}
      </main>
    </div>
  );
}

ビルドとデプロイ

Vite 構成でのビルドとデプロイの流れを解説します。

package.json のスクリプト設定

json{
  "scripts": {
    "dev": "remix vite:dev",
    "build": "remix vite:build",
    "start": "remix-serve ./build/server/index.js",
    "typecheck": "tsc"
  }
}

スクリプトの説明:

#コマンド説明用途
1dev開発サーバを起動ローカル開発
2build本番用ビルドを実行デプロイ前
3startビルド済みアプリを起動本番環境
4typecheckTypeScript 型チェックCI/CD

ビルドの実行

bash# 本番用ビルド
yarn build

このコマンドにより、build​/​ディレクトリに最適化されたファイルが生成されます。

以下の図は、Vite ビルドプロセスの全体像です。

mermaidflowchart TB
  start["yarn build"] --> analyze["ソースコード<br/>解析"]
  analyze --> deps["依存関係<br/>グラフ作成"]
  deps --> transform["TypeScript<br/>→ JavaScript変換"]
  transform --> bundle["Rollupによる<br/>バンドル"]
  bundle --> optimize["コード最適化<br/>・圧縮・Tree-shaking"]
  optimize --> chunks["チャンク分割<br/>vendor/react-vendor"]
  chunks --> output["build/ディレクトリ<br/>出力"]
  output --> done["ビルド完了"]

ビルド出力の確認:

bash# ビルド結果の確認
ls -la build/
bashbuild/
├── client/          # クライアント側のバンドル
│   ├── assets/
│   └── index.html
└── server/          # サーバー側のバンドル
    └── index.js

デプロイ例(Vercel)

Vercel へのデプロイは、Remix × Vite 構成でもスムーズです。

bash# Vercel CLIをインストール
yarn global add vercel
bash# デプロイを実行
vercel --prod

Vercel は自動的にvite buildを実行し、最適化されたアプリケーションをデプロイします。

トラブルシューティング

よくある問題と解決方法をまとめました。

Error: Cannot find module '@/components/Button'

エラーコード: Error: Cannot find module '@​/​components​/​Button'

エラーメッセージ:

vbnetError: Cannot find module '@/components/Button'
    at Module._resolveFilename (internal/modules/cjs/loader.js:xxx)

発生条件: パスエイリアスの設定が不完全な場合に発生します。

解決方法:

  1. vite.config.tsresolve.aliasを確認
  2. tsconfig.jsonpathsを確認
  3. 両方が一致しているか確認
  4. 開発サーバを再起動
bash# 開発サーバを再起動
yarn dev

ビルドエラー:チャンク分割の問題

エラーコード: RollupError: "default" is not exported by ...

発生条件: 外部ライブラリのインポート方法が誤っている場合。

解決方法:

typescript// ❌ 誤った書き方
import lib from 'some-library';

// ✅ 正しい書き方
import * as lib from 'some-library';
// または
import { specificFunction } from 'some-library';

まとめ

Remix × Vite 構成は、開発体験を大きく向上させる素晴らしい選択肢です。 本記事で解説した内容を振り返ってみましょう。

重要なポイント

設定面での要点:

  • vite.config.ts:開発サーバ、ビルド、プラグインを一元管理
  • パスエイリアス:Vite と TypeScript の両方で設定が必須
  • 環境変数VITE_プレフィックスをクライアント側変数に使用

開発面でのメリット:

  • 高速な開発サーバ起動(ミリ秒単位)
  • 安定した HMR による快適な開発体験
  • 豊富な Vite プラグインエコシステムの活用

ビルド面での利点:

  • Rollup による高度な最適化
  • 柔軟なチャンク分割戦略
  • Tree-shaking によるバンドルサイズ削減

次のステップ

Remix × Vite 構成をマスターしたら、以下の発展的なトピックにも挑戦してみてください。

  • Tailwind CSS の統合:Vite プラグインで Tailwind を導入
  • PWA 対応vite-plugin-pwaでプログレッシブ Web アプリ化
  • パフォーマンス計測:Vite-plugin を使った詳細な分析
  • E2E テスト:Playwright との連携

Vite の柔軟性と Remix の開発思想が組み合わさることで、モダンな Web アプリケーション開発がより楽しくなります。 ぜひ、実際のプロジェクトで試してみてくださいね。

関連リンク