Vite+TanstackRouter+Orval+...の環境構築
はじめに
所属している PlayGround というコミュニティにて、企業協賛のハッカソンに出場しました。我々のチームでは、フロントエンドは Vite×React で web アプリ開発をすることになりました。
フロントエンドの技術構成は以下の通り。
- フレームワーク: Vite+React
- UI ライブラリ: shadcn/ui
- CSS: Tailwind CSS
- その他: Orval, Tanstack Router
本記事では、そのプロジェクトの環境構築の手順をまとめます。
環境構築手順
1. Vite
Vite は高速な開発環境を提供するビルドツールです。create-react-app がなくなった今、数少ない React プロジェクト作成手段の 1 つです。
1-1. Vite+React プロジェクトを作成
npm create vite@latest client --template react
Need to install the following packages:
create-vite@6.3.1
Ok to proceed? (y) y
│
◇ Select a framework:
│ React
│
◇ Select a variant:
│ TypeScript
│
◇ Scaffolding project in /Users/luckimai/Desktop/yumemi-pg-hackathon-team3/client...
│
└ Done. Now run:
cd client
npm install
npm run dev
npm notice
npm notice New major version of npm available! 9.8.0 -> 11.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.2.0
npm notice Run npm install -g npm@11.2.0 to update!
npm notice
1-2. 不要なファイルの削除
assets
, index.css
, public/vite.svg
, App.css
などの初期生成ファイルは要らないので削除します。
2. Tailwind CSS
Tailwind CSS は、ユーティリティファーストの CSS フレームワークです。
2-1. tailwind をインストール
npm install tailwindcss @tailwindcss/vite
vite.config.ts
を編集
2-2. import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
});
tailwind.css
を作成
2-3. @import "tailwindcss";
index.html
で CSS ファイルを読み込む
2-4. <link href="/src/tailwind.css" rel="stylesheet" />
なお、index.html ファイル内で favicon なども設定できます。
3. Shadcn/UI
Shadcn/UI は、再利用可能な UI コンポーネントのコレクションで、Tailwind CSS と組み合わせて使用します。
tsconfig.json
とtsconfig.app.json
を編集
3-1. IDE がパス解決できるように// tsconfig.json
{
"files": [],
"references": [
...
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
// tsconfig.app.json
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
// ...
}
}
3-2. アプリがパス解決できるように設定
まず、必要なパッケージをインストールします:
npm install -D @types/node
次に、vite.config.ts
を以下のように編集します:
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
3-3. shadcn をインストール
npx shadcn@latest init
実行結果:
✔ Preflight checks.
✔ Verifying framework. Found Vite.
✔ Validating Tailwind CSS config. Found v4.
✔ Validating import alias.
✔ Which color would you like to use as the base color? › Neutral
✔ Writing components.json.
✔ Checking registry.
✔ Updating src/tailwind.css
Installing dependencies.
It looks like you are using React 19.
Some packages may fail to install due to peer dependency issues in npm (see https://ui.shadcn.com/react-19).
✔ How would you like to proceed? › Use --legacy-peer-deps
✔ Installing dependencies.
✔ Created 1 file:
- src/lib/utils.ts
Success! Project initialization completed.
You may now add components.
4. Tanstack Router
Tanstack Router は、型安全でパフォーマンスに優れた React ルーティングライブラリです。React Router と異なり、ファイルベースのルーティングに対応しているので採用しました。
4-1. 必要なパッケージをインストール
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin @tanstack/router-devtools
vite.config.ts
を編集
4-2. import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
export default defineConfig({
plugins: [
TanStackRouterVite({ target: "react", autoCodeSplitting: true }),
react(),
],
});
4-3. ルーティング用のページを作成
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
export const Route = createRootRoute({
component: () => (
<>
<div className="p-2 flex gap-2">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</div>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
),
});
// src/routes/index.tsx
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/")({
component: Index,
});
function Index() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
);
}
// src/routes/about.tsx
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/about")({
component: About,
});
function About() {
return <div className="p-2">Hello from About!</div>;
}
App.tsx
を削除
4-4. 不要になった
main.tsx
を編集
4-5. import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";
// Import the generated route tree
import { routeTree } from "./routeTree.gen";
// Create a new router instance
const router = createRouter({ routeTree });
// Register the router instance for type safety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
// Render the app
const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);
}
4-6. 動作確認
npm run dev
を実行すると、自動的に routeTree.gen.ts
が生成されます。ブラウザで表示されたアプリにホームとアバウトページへのリンクが表示され、クリックでページ移動ができれば成功です。
5. 環境変数のセットアップ
アプリケーションで使用する環境変数を安全に管理するための設定を行います。
.env.example
の作成
5-1. リポジトリをクローンした際に、cp .env.example .env.local
でファイルをコピーして、.env.local
に必要な値を入れていきます。
VITE_API_URL= #VITEプレフィックスをつけること
VITE_WS_API_URL=
VITE_USER_POOL_ID=
VITE_USER_POOL_CLIENT_ID=
vite-env.d.ts
の編集
5-2. 作成した環境変数の型定義を追加します:
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_URL: string;
readonly VITE_WS_APP_URL: string;
readonly VITE_API_CLIENT_ID: string;
readonly VITE_API_CLIENT_SECRET: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
env.ts
を作成
5-3. 定義した環境変数の型を確定させ、名称を変更します:
import * as z from "zod";
const initEnv = () => {
const envSchema = z.object({
API_URL: z.string(),
WS_API_URL: z.string(),
USER_POOL_ID: z.string(),
USER_POOL_CLIENT_ID: z.string(),
});
const envVars = {
API_URL: process.env.VITE_API_URL,
WS_API_URL: process.env.VITE_WS_API_URL,
USER_POOL_ID: process.env.VITE_USER_POOL_ID,
USER_POOL_CLIENT_ID: process.env.VITE_USER_POOL_CLIENT_ID,
};
// スキーマでバリデーションしながらパース
const parsedEnv = envSchema.safeParse(envVars);
if (!parsedEnv.success) {
throw new Error("環境変数の設定エラー");
}
return parsedEnv.data;
};
export const env = initEnv();
6. Orval
Orval は、OpenAPI(Swagger)スキーマから TypeScript の型定義と API フックを自動生成するツールです。API との連携を型安全に、簡単に行うことができます。
6-1. Orval のインストール
npm i orval -D
orval.config.ts
の作成
6-2. import { defineConfig } from "orval";
export default defineConfig({
waLive: {
input: {
target: "../server/http_schema.yml",
},
output: {
mode: "tags-split", // メソッド毎にフォルダを分けてくれる
clean: true,
client: "axios", // axios以外にもclientは色々ある。今回はtanstack-query使っていないのでaxios
target: "src/hooks/orval", // フックの作成先
override: {
mutator: {
path: "src/lib/custom-instance.ts", // custom-instanceを元に作成
name: "customInstance",
},
},
prettier: true, // 作成したフックに対して自動でprettierを効かせてくれる
},
},
});
custom-instance.ts
の作成
6-3. Axios インスタンスをカスタマイズして、認証トークンの自動付与やエラーハンドリングを設定します:
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { toast } from "react-toastify";
import { env } from "../env";
// envを読み込む時はこうする
const { API_URL, USER_POOL_CLIENT_ID } = env;
// Axios インスタンスの作成
const instance: AxiosInstance = axios.create({
baseURL: API_URL,
headers: {
"Content-Type": "application/json",
},
});
// リクエストインターセプター - すべてのリクエストに認証トークンを追加
instance.interceptors.request.use(
(config) => {
// ローカルストレージからアクセストークンを取得
const userId = localStorage.getItem(
`CognitoIdentityServiceProvider.${USER_POOL_CLIENT_ID}.LastAuthUser`
);
const idToken = localStorage.getItem(
`CognitoIdentityServiceProvider.${USER_POOL_CLIENT_ID}.${userId}.idToken`
);
// トークンが存在する場合、認証ヘッダーに追加
if (idToken && config.headers) {
config.headers["Authorization"] = `Bearer ${idToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// レスポンスインターセプター - エラーハンドリング
instance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
console.error("認証エラー", error.response);
toast.error("認証エラーが発生しました。再ログインしてください。");
window.location.href = "/login";
}
return Promise.reject(error);
}
);
// Orval 用のエクスポート関数
export const customInstance = <T>(
config: AxiosRequestConfig
): Promise<AxiosResponse<T>> => {
return instance.request<T>(config);
};
6-4. フックの自動生成
OpenAPI スキーマから TypeScript コードを生成するには、以下のコマンドを実行します:
npx orval
また、設定ファイルへのパスを明示的にする場合は、
"orval": "orval --config ./orval.config.ts"
のように script に登録して、npm run orval で実行してやるのがオススメです。
実行後、src/hooks/orval
ディレクトリに API クライアントコードが生成されます。
使用する際は、例えば以下のようなスキーマのエンドポイントであれば、
/users/profile/{userId}:
put:
tags:
- Users
summary: ユーザーのプロフィール登録(初回ログイン時)および更新
description: 初回ログイン時に追加情報(ユーザー名、プロフィール画像、背景色)を登録・更新。
parameters:
- in: path
name: userId
schema:
type: string
required: true
description: ユーザーの一意識別子
- in: header
name: Authorization
schema:
type: string
required: true
description: "Authorization: Bearer eyJraWQiOiJrZXkxIiwiYWxnIjoiUlMyNTYifQ...(IDトークン)"
requestBody:
description: プロフィール更新情報
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserProfileUpdate"
responses:
"200":
description: プロフィール登録成功。
UserProfileUpdate:
type: object
properties:
userName:
type: string
description: ユーザーの表示名
profileImage:
type: string
description: プロフィール画像のURL
profileColor:
type: string
description: プロフィールの背景色
required:
- userName
users フォルダの中の users.ts
というファイルを参照し、putUsersProfileUserId(メソッド/カテゴリ/エンドポイント)をインポートすれば良いということになります。
import { getUsers } from "@/hooks/orval/users/users";
const { putUsersProfileUserId } = getUsers();
// paramやrequestBodyはこのように渡す
await putUsersProfileUserId(userId, {
userName: formData.userName,
profileImage: formData.profileImage,
profileColor: formData.profileColor,
});
以上です。
Discussion