はじめに
Viteで React + TypeScript プロジェクトを作成すると、main.tsxというファイルが自動生成されます。このファイル、実は「アプリ全体の土台を管理する特別なファイル」なんです。
一言でいうと、「アプリ全体に関わる"土台まわり"を変えたいときだけ main.tsx を触る」という位置づけです。普通の画面追加やコンポーネント作成では、基本的にいじりません。
この記事では、main.tsxの役割と「どんなときに編集するのか」を具体例とともに解説します。
main.tsxの基本的な役割
アプリケーションのエントリーポイント
main.tsxは、Reactアプリケーションが起動する**最初の地点(エントリーポイント)**です。Viteで生成される初期コードは次のようになっています。
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
Reactアプリの起動フロー
ブラウザでアプリが表示されるまでの流れを図で見てみましょう。
- ブラウザが
index.htmlを読み込む -
index.html内でmain.tsxが読み込まれる -
main.tsxがReactアプリ(<App />)をDOMにマウントする - 画面に表示される
初期コードの各部分の役割
// DOM要素を取得してReactのルートを作成
createRoot(document.getElementById('root')!)
// renderメソッドでReactコンポーネントをマウント
.render(
<StrictMode> // 開発時の警告を表示するラッパー
<App /> // アプリケーションのメインコンポーネント
</StrictMode>,
)
この構造が、Reactアプリの「起動の土台」になっているわけですね。
main.tsxを編集する5つの代表的なケース
それでは、実際にmain.tsxを編集する具体的なケースを見ていきましょう。
※特別な理由がない限り編集しないことが推奨されます。
① ルーティングライブラリや状態管理を導入するとき
React Routerを使いたいときなど、<App />の外側にライブラリのProviderを巻きたいときにmain.tsxを書き換えます。
React Routerの例:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
)
その他の代表的なライブラリ:
-
Redux:
<Provider store={store}> -
React Query:
<QueryClientProvider client={queryClient}> -
Recoil:
<RecoilRoot> -
MUI / Chakra UI:
<ThemeProvider> -
i18n:
<I18nextProvider>
これらは**「アプリ全体に効かせたいもの」なので、main.tsxでラップする**のが定石です。
Provider階層構造の可視化
複数のProviderを組み合わせる場合の階層イメージはこうなります。
外側から順に、アプリ全体を包み込んでいくイメージですね。
② StrictModeのON/OFFを切り替えたいとき
StrictModeとは?
StrictModeは、React開発時に潜在的な問題を検出するための機能です。開発モードでは次のような挙動をします。
- コンポーネントが2回レンダリングされる
- 副作用(useEffect等)が2回実行される
- 非推奨APIの使用時に警告が表示される
開発中のダブルレンダーが気になる、一時的に副作用の挙動を確認したい…そんなときにStrictModeを外したり戻したりします。
// 開発中に一時的にStrictModeをやめたいとき
createRoot(document.getElementById('root')!).render(
// <StrictMode>
<App />
// </StrictMode>,
)
※本番ビルドには影響しませんが、「挙動が分かりづらいので一旦外したい」ときに触るポイントです。
③ マウント先のDOMを変更したいとき
index.html側でid="root"以外に変えた場合や、複数の場所に別々のReactアプリをマウントしたい場合もmain.tsxを変更します。
カスタムルート要素の指定:
// 例: #root-app というIDに変えた場合
createRoot(document.getElementById('root-app')!).render(
<App />
)
複数マウントするケース:
const root = document.getElementById('root')!
const adminRoot = document.getElementById('admin-root')!
createRoot(root).render(<App />)
createRoot(adminRoot).render(<AdminApp />)
同一ページ内で、ユーザー向けアプリと管理画面を別々に表示したい場合などに使います。
④ SSRやhydrateを使う構成にしたいとき
Next.jsではなく自前でSSR(サーバーサイドレンダリング)したい、といった場合は次のような変更が必要になります。
- クライアント側で
createRootではなくhydrateRootを使う - エントリーポイントの書き方を変える
hydrateRootの実装例:
import { hydrateRoot } from 'react-dom/client'
hydrateRoot(
document.getElementById('root')!,
<App />
)
SSRとの関係性:
SSRでは、サーバー側で生成されたHTMLに対して、クライアント側でReactのイベントハンドラーを「水和(hydrate)」させます。
⑤ グローバルな設定を追加したいとき
アプリ起動時に一度だけ実行したい処理を追加するときもmain.tsxに書きます。
代表的な例:
- CSSフレームワークのエントリ読み込み(Tailwind、reset CSSなど)
- Sentryなどのエラートラッキングの初期化
- ログ設定やモニタリングツールの初期化
import './index.css'
import 'the-new-reset.css'
// Sentryの初期化例
import * as Sentry from "@sentry/react"
Sentry.init({
dsn: "your-dsn-here",
integrations: [new Sentry.BrowserTracing()],
tracesSampleRate: 1.0,
})
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
こうした「アプリ全体に影響する初期設定」は、main.tsxで行うのが適切です。
main.tsxに書くべきでないこと
main.tsxはブートストラップ専用のファイルと考えるとスッキリします。
次のような内容は、main.tsxには書かない方がよいでしょう。
❌ 書くべきでないもの
-
画面のルーティングの詳細
- ルート定義は
App.tsxやroutes配下のファイルに
- ルート定義は
-
普通のUIロジックや状態管理
- コンポーネント内の状態は各コンポーネントファイルに
-
ビジネスロジック
- ドメインロジックは専用のディレクトリに
⭕ 正しい配置
こういった構造を意識すると、**main.tsxは「アプリ全体の配線・土台だけ」**にしておくことができ、後から見たときに理解しやすくなります。
まとめ
main.tsxを修正するのは、次のようなときです:
- React Router / Redux / React Query / ThemeProvider など、アプリ全体をラップするProviderを追加したいとき
- StrictModeの有効・無効を切り替えたいとき
- マウント先のDOMや複数マウント構成に変えたいとき
- SSR / hydrateRootなど、レンダリング方式を変えたいとき
- グローバルなCSSや初期化処理を追加したいとき
それ以外の「普通の機能追加」は、基本的にApp.tsx以下に書くイメージで大丈夫です。
main.tsxは「アプリ全体の土台だけ」を管理する特別なファイルとして、その役割を明確にしておくことが大切ですね。