Laravel

Laravel Viteの使い方|@viteの書き方と本番ビルドの落とし穴

LaravelでViteを使うとき、つまずく場所はだいたい決まっています。vite.config.js にどう書くのか、Bladeの @vite はどこに置くのか、開発中はどのURLを開けばいいのか、そして npm run build したのに本番でCSSが当たらないのはなぜか。この記事では、Laravel 13・laravel-vite-plugin 3.1.3・Vite 8.2.0という2026年8月時点の現行構成にそろえて、設定の最小形から本番デプロイで壊れる典型パターン、Laravel Mixからの移行判断までを順に整理します。

まとめ

  • Laravel Viteとは、Laravelが公式採用したフロントエンドビルドツールViteと、連携用の公式プラグイン laravel-vite-plugin を組み合わせた構成を指します。laravel/framework 9.19.0以降、新規インストール時の標準です。
  • npmの最新版はlaravel-vite-plugin 3.1.3(2026年7月13日公開)で、peerDependenciesはVite 8系(^8.0.0)です。
  • Laravel Mixは、Laravel 13の公式ドキュメントで「積極的にメンテナンスされていないレガシーパッケージ」と明記されています。新規プロジェクトでMixを選ぶ理由はありません。
  • Bladeでは @vite<head> 内に1回だけ書きます。開発中にブラウザで開くのはアプリのURLで、Viteの開発サーバ(既定5173番)ではありません。
  • 本番でアセットが読み込まれない原因の大半は2つです。public/build/manifest.json が無い(ビルド漏れ)か、public/hot が消し忘れで残っているかです。

LaravelがViteを標準採用した経緯とLaravel Mixの現在地

Vite標準化のバージョン境界と現行の組み合わせ

LaravelがフロントエンドのビルドをMixからViteへ切り替えたのは、laravel/framework の9.19.0からです。公式の移行ガイドも「Viteの新しい統合を使うには少なくとも9.19.0へ更新する必要がある」と書いています。

2026年8月時点の現行構成は、npmで公開されているバージョンを基準にすると次のとおりです。

パッケージ 最新版 公開日 備考
vite 8.2.0 2026-07-30 前系列は7.3.6
laravel-vite-plugin 3.1.3 2026-07-13 peerはvite ^8.0.0
laravel-mix 6.0.49 2022-06-09 4年以上新規公開なし

Vite 7以前のまま3.1.3を入れると、npm 7以降では ERESOLVE でインストール自体が失敗します(--legacy-peer-deps を付けたときだけ警告で通過)。プラグインを上げるならViteも一緒に上げてください。

Laravel Mixに対する公式の現在の位置づけ

Laravel 13のドキュメントにあるMixのページは、冒頭でこう述べています。「Laravel Mixは、もはや積極的にメンテナンスされていないレガシーパッケージです。モダンな代替としてViteを使用できます」。続けて「新規のLaravelインストールではViteがLaravel Mixを置き換えました」とも書かれています。

つまり「Mixなら公式サポートが受けられる」という説明は、現在の公式見解と食い違います。npmでの最終公開が2022年6月であることも、この位置づけを裏づけています。既存プロジェクトでMixが動いている分にはすぐ壊れませんが、新規に選ぶ対象ではありません。

PHPアプリにおけるViteの守備範囲

Viteはブラウザへ配信するCSSとJavaScriptをまとめるツールであり、PHPのソースには一切触れません。Laravel側は @vite ディレクティブで「開発中はViteの開発サーバを、本番はビルド済みファイルを読み込む」という切り替えを担当します。

Viteを導入してもPHPの実行環境は変わりません。変わるのは resources 配下のアセットが public/build へ出力される点と、開発中のリロード体験だけです。

laravel-vite-pluginの導入とvite.config.jsの最小構成

インストール手順とNode.jsの実際の要件

Laravelのスターターキットを使った場合は package.jsonvite.config.js が最初から用意されているため、npm install だけで済みます。既存プロジェクトへ手で入れる場合は次のコマンドです。

npm install --save-dev vite laravel-vite-plugin
npm pkg set type="module"

2行目を忘れると、Vite設定ファイルの読み込みでESモジュール関連のエラーが出ます。公式の移行ガイドでもこのコマンドが手順に含まれています。

Node.jsのバージョンは注意が必要でした。公式ドキュメントは今も「Node.js (16+)」と書いていますが、この記載は現行パッケージに追いついていません。laravel-vite-plugin 3.1.3とVite 8.2.0はどちらも engines^20.19.0 || >=22.12.0 を宣言しており、Node 16では npm install が EBADENGINE で止まります。実際に必要なのは20.19以上、または22.12以上です。

エントリポイントの指定方法とSPAでの書き分け

laravel-vite-pluginは、エントリポイントの指定を必須にしています。最小構成は次のとおりです。

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel([
            'resources/css/app.css',
            'resources/js/app.js',
        ]),
    ],
});

import元のパッケージ名は laravel-vite-plugin です。名前がよく似た vite-plugin-laravel は公式のものではないので、コピー元を間違えないよう注意してください。出力先の build ディレクトリはプラグインが自動で設定するため、build.outDir を手書きする必要はありません。

InertiaなどのSPAを組む場合、公式は「CSSのエントリポイントを持たない構成が最も良く動く」としています。resources/js/app.js の側で import '../css/app.css'; と書き、プラグインにはJSだけを渡してください。

Blade保存時に自動リロードするrefreshオプション

Bladeで画面を組む従来型の構成では、refresh を有効にすると保存時にブラウザが自動更新されます。

laravel({
    input: 'resources/js/app.js',
    refresh: true,
})

true にしたとき監視対象になるのは app/Livewireapp/View/Componentslangresources/langresources/viewsroutes の各ディレクトリです。routes が含まれているのは、Ziggyでルート情報をフロントへ渡す構成を想定しているため。監視対象を絞りたいときは、真偽値の代わりにパスの配列を渡します。

エイリアス@の既定値と上書き手順

laravel-vite-pluginは @/resources/js へ向けるエイリアスを自動で追加します。TypeScriptで resources/ts を使うなど別の場所に置きたい場合だけ、resolve.alias で上書きしてください。

Mixから移行するときの落とし穴として、エイリアスのパスはスラッシュで始める必要があります。webpack.mix.jsresources/js と書いていたなら、Vite側では /resources/js に直します。

@viteディレクティブの記述位置と開発時のアクセス先

@viteの正しい書き方と重複記述の害

Bladeのルートテンプレートの <head> 内に、次のように1回だけ書きます。

<!DOCTYPE html>
<head>
    {{-- ... --}}

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>

CSSをJavaScript側でインポートしている構成なら、JSのパスを文字列で1つ渡すだけで足ります。

ときどき見かける誤りが、同じ @vite<head><body> の両方に書いてしまうパターン。同一URLのscriptタグとlinkタグが重複して出力され、開発時には @vite/client まわりの警告が増えます。書く場所は <head> 内の1か所だけにしてください。

開発サーバのポート5173とアプリURLの役割分担

npm run dev で起動するViteの開発サーバは、既定でポート5173を使います(Vite公式の server.port の既定値)。ただし、そのポートが埋まっていた場合は自動的に次の空きポートへ繰り上がるため、5174や5175になることもあります。番号を固定したいなら server.strictPorttrue にしてください(使用中なら起動せずに終了します)。

そして、ブラウザで開くのはこのURLではありません。開くのはLaravelアプリのURL、php artisan serve なら8000番、HerdやValetを使っているならそのホスト名です。Bladeが返したHTMLの @vite が開発サーバを検出し、HMR用のクライアントスクリプトを差し込む流れだからです。

5173番を直接開いた場合は、プラグインに同梱された案内ページが返ります。laravel-vite-pluginのソースでは、リクエストURLが /index.html のときに dev-server-index.html を読み込み、{{ APP_URL }} を実際の値へ置換して返す処理が入っています。表示されるのは「これはVite開発サーバです」という趣旨の説明と、設定済みの APP_URL の値。そこに出ているURLを開き直せば、アプリの画面にたどり着けます。

11.x系以降のアプリケーションスケルトンには composer run dev が同梱されています。公式ドキュメントは「LaravelとViteの開発サーバを両方起動する」と説明しますが、11.xの composer.json の実体は concurrently による4プロセス(servequeue:listenpailnpm run dev)の並走です。

composer run dev

Blade専用の静的アセットを扱うassetsオプション

Bladeテンプレートからしか参照しない画像は、JavaScriptを経由しないためVite側が存在を認識できません。laravel-vite-pluginの3系では、assets オプションで対象を宣言する方式が導入されました。

laravel({
    input: 'resources/js/app.js',
    assets: ['resources/images/**', 'resources/fonts/**'],
})

これで npm run build の対象に含まれ、Blade側から Vite::asset('resources/images/logo.png') でバージョン付きURLを取得できます。3系より前は import.meta.glob をエントリポイントへ書く必要がありました。

フォントは同じグロブ指定ではなく別系統です。3.0.3以降、laravel-vite-plugin/fonts から google() などのプロバイダ関数をimportして fonts オプションへ渡す方式が追加され、Blade側は @fonts ディレクティブで受け取ります。

VueとReactを組み合わせるときの追加設定

Vueで必須のtransformAssetUrls設定

Vueを使う場合は、まずプラグインを追加します。

npm install --save-dev @vitejs/plugin-vue

ただし、そのまま並べるだけでは不十分でした。次の2つのオプションが要ります。

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel(['resources/js/app.js']),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
});

公式のコードコメントによれば、Vueプラグインは単一ファイルコンポーネント内のアセットURLをLaravelのWebサーバ向きに書き換えます。base: null はこれを止め、Laravel側のプラグインにVite開発サーバ向きの書き換えを任せる指定です。includeAbsolute: false は絶対URLをそのまま残し、public ディレクトリのファイルを期待どおり参照できるようにします。

Reactプラグインの選択とVite 8での注意点

Reactの場合は @vitejs/plugin-react を追加し、laravel() と並べるだけで動きます。ただしプラグイン側の世代交代が進んでおり、選定基準はバージョンによって変わりました。詳細は@vitejs/plugin-react とは?v6(Vite 8・Babel廃止)の導入設定と plugin-react-swc との違いにまとめています。

Inertiaで一体化するか、APIとSPAに分けるかから迷っている場合は、LaravelとReactの連携ガイドで構成別の判断材料を整理しています。

本番ビルドとデプロイで壊れる典型パターン

npm run buildの出力先とmanifest.jsonの実パス

本番用のビルドは npm run build です。出力先はLaravel側の既定で決まっており、フレームワークのソース(Illuminate\Foundation\Vite)ではビルドディレクトリが build、マニフェストのファイル名が manifest.json と定義されています。実体のパスは public/build/manifest.json です。

デプロイ時に public/build をまるごと転送しているか、あるいはデプロイ先でビルドを実行しているかを、まず確認してください。.gitignorepublic/build を入れたまま「リポジトリを配置するだけ」のデプロイをしていると、この段階で必ず失敗します。

Vite manifest not foundの原因切り分け

本番で画面を開いたときに出る「Vite manifest not found at:」という例外は、上のマニフェストファイルが見つからなかったという意味です。

まずエラーメッセージに続くパスを読み、想定どおり public/build/manifest.json を指しているか確認します。次に、そのファイルがサーバ上に実在するかを見ます。ここまでで大半は解決するはず。それでも出るならWebサーバの実行ユーザに読み取り権限があるかを疑ってください。なお例外クラスの ViteManifestNotFoundExceptionViteException を継承したまま非推奨(deprecated)になっており、ソース上も ViteException を使うよう指示されています。捕捉するなら ViteException 側に寄せます。

publicディレクトリに残ったhotファイルによる白画面

マニフェストは正しいのに本番でアセットが読み込まれない、という場合はこちらを疑ってください。npm run dev を実行すると、Laravelは public 直下に hot というファイルを作ります。フレームワークのソースでも、hotファイルの位置は public_path('/hot') と定義されています。

このファイルが存在する間、@vite は「開発サーバが動いている」と判断し、本番環境であってもローカルの開発サーバのURLを出力し続けます。結果として、外部からアクセスしたユーザーにはCSSもJSも読み込まれない画面が返ります。デプロイ手順に削除を組み込んでおくのが確実です。

rm -f public/hot

CDN配信時のASSET_URL指定

ビルド成果物をアプリ本体とは別ドメイン(CDNなど)へ置く場合は、.envASSET_URL を設定します。設定するとアセットのURLがその値で前置きされ、https://cdn.example.com/build/assets/app.9dce8d17.js のような形になります。ただしコード中に絶対URLで書いたパスはVite側で書き換えられないため、前置きの対象にもなりません。

SSRとテスト実行時のVite設定

SSRエントリポイントの指定とビルドコマンド

LaravelでサーバサイドレンダリングをするときのViteの役割は、SSR用エントリポイントを別途ビルドすることです。Node側のサーバ実装を自分で書き起こす必要はありません。resources/js/ssr.js を作り、プラグインに渡します。

laravel({
    input: 'resources/js/app.js',
    ssr: 'resources/js/ssr.js',
}),

ビルドスクリプトはSSR分を追加した形にしておきましょう。"build": "vite build && vite build --ssr" と書いておけば、SSR側のビルド漏れを防げます。生成物は bootstrap/ssr/ssr.js に出力され、node bootstrap/ssr/ssr.js で起動します。

Inertia利用時のSSRサーバ起動

InertiaでSSRを組んでいる場合は、Node.jsのコマンドを直接叩く代わりにArtisanコマンドが使えます。

php artisan inertia:start-ssr

プロセス管理をSupervisorなどに任せるとき、この形のほうがLaravel側の設定と揃えやすくなります。なおLaravelのスターターキットには、Inertia SSRとViteの設定が最初から含まれています。スターターキットの選択肢は2026年に変わったので、新規構築ならLaravel Breeze・Jetstreamはなぜ非推奨に?スターターキットへの移行と代替案で現行の選び方を確認してください。

テストでViteを無効化するwithoutVite

FeatureテストでBladeを描画すると、Laravelはアセットの解決を試みます。ビルドも開発サーバも無い状態でテストを回せば、ここでマニフェスト由来の例外が出るわけです。回避するにはテスト内で $this->withoutVite(); を呼びます。全テストで無効化したい場合は、基底の TestCasesetUp で呼んでおけば済みます。

フロント側のユニットテストをVitestで書いているなら、実行環境の設定は別途必要。Vitest 4の新機能と移行ガイドにブラウザモードの正式化を含めた変更点をまとめています。

Laravel MixからViteへ移行する手順と判断基準

移行時に書き換える4か所

公式の移行ガイドに沿うと、実作業は次の4か所に集約されます。

  • package.jsonのscriptsmix 系のスクリプト群を "dev": "vite""build": "vite build" の2つに置き換えます。
  • JavaScriptのモジュール形式:ViteはESモジュールのみを扱うため、require() をすべて import へ書き換えます。ここが移行作業の実質的な山場です。
  • 環境変数の接頭辞.env でフロントへ露出させている変数を MIX_ から VITE_ へ変更します。参照側も import.meta.env.VITE_XXX の形になります。
  • エイリアスのパス:先述のとおり、スラッシュ始まりに直します。

移行を急がなくてよい場合の条件

稼働中のプロジェクトでMixが問題なく動いており、フロントの変更頻度が低いなら、移行を今すぐ行う必要はありません。Mixは非推奨扱いですが即座に動かなくなるものではなく、移行作業の中心である require() の書き換えは、コードベースが大きいほど回帰リスクを伴うからです。

逆に、Node.jsのメジャー更新でwebpackまわりのビルドが通らなくなり始めているなら、そこが実質的な期限です。ビルドが壊れてから移行すると、修復と移行を同時に進めることになります。フロントのライブラリを頻繁に追加・更新するプロジェクトでも前倒しの価値があります。Mixは公式ドキュメントから実質的に切り離されており、詰まったときに参照できる一次情報が減り続けるからです。

SailやDocker環境でHMRが効かないときの設定

WSL2上のSailで指定するhmr.host

Laravel SailをWSL2で動かしている場合、ブラウザから開発サーバへの接続がうまくいかず、HMRが効かないことがあります。公式ドキュメントが案内している対処は、HMRのホストを明示することです。

export default defineConfig({
    // ... plugins などは既存のまま
    server: {
        hmr: {
            host: 'localhost',
        },
    },
});

ファイル変更が検知されない場合のポーリング設定

上の設定を入れても保存が反映されないときは、コンテナ内のファイル監視がホスト側の変更を拾えていない可能性があります。この場合はViteの server.watch.usePolling を有効にします。

server: {
    watch: {
        usePolling: true,
    },
},

ポーリングは常時ファイルを走査するためCPU使用率が上がります。ignorednode_modulesvendor を除外し、監視対象を絞ったうえで使ってください。

よくある質問

Viteの開発サーバのポートを5173に固定できますか?

できます。server.strictPorttrue にすると、5173が使用中のときに次のポートへ繰り上がらず、その場で終了します。既定の false のままだと自動で空きポートを探すため、複数プロジェクトを並行して立ち上げているとポート番号が変動します。

画面ごとにエントリポイントを分けられますか?

分けられます。laravel-vite-pluginは複数のエントリポイント指定に対応しており、laravel([...]) に配列で列挙したうえで、Blade側の @vite でも必要なものだけを配列で指定します。管理画面と公開画面でJavaScriptを分離したい場合はこの形です。

.envの値をJavaScript側から読めますか?

読めますが、接頭辞が VITE_ のものに限られます。.envVITE_SENTRY_DSN_PUBLIC=http://example.com のように書き、JavaScript側では import.meta.env.VITE_SENTRY_DSN_PUBLIC で参照します。接頭辞のない変数はビルド結果に含まれません。逆に言えば、VITE_ を付けた値はブラウザから見えるので、秘匿情報を入れてはいけません。

webpack.mix.jsの設定はそのまま移せますか?

そのままは移せません。公式の移行ガイド自体が冒頭で「Sassのコンパイルなど、Mixのすべてのユースケースを網羅していない」と断っています。エントリポイントとエイリアスは対応関係が明確ですが、Mix独自のメソッドで組んだ処理はVite側のプラグインで組み直す前提で見積もってください。

本番環境でもViteの開発サーバを動かす必要がありますか?

不要です。npm run dev はローカル開発用で、本番に必要なのは npm run build が生成した public/build 配下の成果物だけです。

関連記事

資料請求

RELATED POSTS 関連記事