ホーム>Laravel>Laravel Precognitionとは?Vue+Inertiaでリアルタイムバリデーションを実装してみた
Laravel

Laravel Precognitionとは?Vue+Inertiaでリアルタイムバリデーションを実装してみた

いつもご利用ありがとうございます。
この記事には広告が掲載されており、その広告費によって運営しています。

Laravel公式機能「Precognition」の仕組みと、Vue+Inertia構成で実際にリアルタイムバリデーションフォームを実装・検証した記録です。

関連動画

質問やフィードバック

この記事や動画に関する質問やフィードバックあれば、動画のコメント欄にてお気軽にコメントしてください。

Laravel 13 系のドキュメントを眺めていたら、「Precognition」という機能が目に留まりました。

フォームを実際には送信・保存せずに、バックエンドのバリデーションだけを先読み実行できる仕組みだそうです。

SPA でよくある「フロント用の入力チェック」と「バックエンドのバリデーションルール」の二重管理を避けられそうだったので、実際に Docker 上で Vue + Inertia のスターターキットを使い、リアルタイムバリデーションフォームを 1 つ実装してみました。

FormRequest のルールをそのまま先読みに使い回せる仕組みらしい

Precognition は Laravel 本体に組み込まれている機能で、追加パッケージなしでバックエンド側は使えます。

Laravel 13.x Precognition

公式ドキュメントによると、通常のバリデーションは

  • JavaScript などで入力を監視して無効の入力があれば表示
  • 入力を送信 → サーバーで検証 → エラーがあれば返す

という、フロントとバックエンドを別で実装する流れになりがちなところ、Precognition を使うと、入力中に「今のところこのフィールドは有効か」だけをサーバーに問い合わせられる、とのことです。

ポイントは、実際に使っている FormRequest のルールをそのまま使い回せることです。

フロント側で同じルールを再実装する必要がなく、unique のような DB 参照が必要なルールも、先読みの時点で本物の検証が走ります(これは後述の重複メールのスクリーンショットで実際に確認できました)。

Docker 上に Laravel + Vue + Inertia の検証環境を作った

1. Vue + Inertia スターターキットでプロジェクト作成

laravel new laravel-precognition-demo --vue --database=sqlite --no-authentication --pest

執筆時点で Laravel 13.26.1 + Inertia 3 系がインストールされました。

2. バックエンド側は追加パッケージ不要だった

必要だったのは、bootstrap/app.phpweb ミドルウェアグループに HandlePrecognitiveRequests を追加するだけです。

use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; //追加

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        HandleInertiaRequests::class,
        AddLinkHeadersForPreloadedAssets::class,
        HandlePrecognitiveRequests::class,  //追加
    ]);
})

vendor/laravel/framework の中身を見てみると、Illuminate\Foundation\Http\FormRequest に Precognition 対応のコードが既に組み込まれていたため、これで OK です。

3. イベント参加申し込みフォームを実装した

検証用に、名前・メールアドレス・参加人数を登録する簡単なフォームを作りました。

unique のバリデーションは、フロント側だけでは絶対に判定できないので、Precognition の効果が分かりやすいと思いこんな感じにしてみました。

// app/Http/Requests/StoreSignupRequest.php
public function rules(): array
{
    return [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'email', 'max:255', 'unique:signups,email'],
        'guests' => ['required', 'integer', 'min:1', 'max:5'],
    ];
}

コントローラーとルートはごく普通の Laravel + Inertia の実装です。

// app/Http/Controllers/SignupController.php
public function store(StoreSignupRequest $request): RedirectResponse
{
    Signup::create($request->validated());

    return redirect()->route('signup.create')->with('success', 'お申し込みありがとうございます。');
}

公式ドキュメントによると、Precognition のリクエストでも FormRequestauthorize() は通常どおり評価されるとのことです。

今回の StoreSignupRequest には authorize() を定義していませんでしたが、独自の認可ロジックを書く場合は、先読みリクエストの分だけ余計に呼ばれる点は意識しておいた方がよさそうです。

4. フロント側パッケージを導入した

npm install laravel-precognition-vue-inertia --legacy-peer-deps

--legacy-peer-depsを付けた理由

npm install laravel-precognition-vue-inertia

と入れようとしたところ、npm install が失敗しました。

npm error Found: @inertiajs/vue3@3.7.0
npm error Could not resolve dependency:
npm error peer @inertiajs/vue3@"^1.0.0 || ^2.0.0" from laravel-precognition-vue-inertia@0.8.0

バージョン不整合に遭遇しました。

執筆時点の最新版 laravel-precognition-vue-inertia@0.8.0 は、peerDependencies@inertiajs/vue3 の v1・v2 までしか許可しておらず、現行のスターターキットが使う Inertia v3 とは正式には噛み合っていないようです。

--legacy-peer-deps を付ければインストール自体は通り、後述の通り動作も問題なかったので、今のところ実害はなさそうですが、Precognition の Vue 用ヘルパーが Inertia v3 に追随できていない、という状態は把握しておいた方がよさそうです。

Inertia を使っているか、従来の axios を使っているか

Precognition のフロント用ヘルパーは、

  1. Vue 単体向けの laravel-precognition-vue
  2. Vue + Inertia 向けの laravel-precognition-vue-inertia

の 2 種類があります。

今回は Inertia を使う構成なので、後者を選びましたが、環境によっては、前者を選ぶ必要があります。

5. Vue 側の実装

useForm を Precognition 版に差し替え、各入力欄の change イベントで form.validate() を呼ぶだけで、リアルタイムバリデーションが動きます。

<script setup lang="ts">
//import { useForm } from "@inertiajs/vue3"; 通常のInertiaはコレ
import { useForm } from "laravel-precognition-vue-inertia"

const form = useForm("post", "/signup", {
  name: "",
  email: "",
  guests: 1,
})
</script>

<template>
  <input v-model="form.email" @change="form.validate('email')" />
  <p v-if="form.invalid('email')">{{ form.errors.email }}</p>
</template>

form.invalid('email') でエラーの有無を判定でき、form.errorsFormRequest 側のバリデーションメッセージがそのまま入ってくるので、以下でバリデーション内容をユーザーに表示しています。

<p v-if="form.invalid('email')">{{ form.errors.email }}</p>

不正なメールと登録済みメールで実際にリアルタイムエラーが出るか確認した

php artisan serve でサーバーを立ち上げ、ブラウザで実際のフォーム操作を再現しながらスクリーンショットを撮りました。

初期状態です。

laravel precognition 01 initial

不正な形式のメールアドレスを入力してフォーカスを外すと、送信していないのに即座にエラーが表示されます。

laravel precognition 02 invalid email

事前に登録しておいた既存のメールアドレスを入力すると、こちらもリアルタイムで「既に登録されています」エラーが出ます。

laravel precognition 03 duplicate email

これはフロント側の入力チェックだけでは絶対にできません。

DB に重複の確認をした結果です。

未登録のメールアドレスに直すと、エラーが消えます。

laravel precognition 04 valid

そのまま送信すると、通常どおり DB に保存され、成功メッセージが表示されました。

laravel precognition 05 success

デバウンスが 1.5 秒もあった

スクリーンショットを撮るためにフォームへ連続して入力を流したところ、操作間隔が短いと後続のリクエストが発火しないことに気づきました。

laravel-precognition パッケージの中身(validator.js)を見てみると、debounceTimeoutDuration = 1500 がデフォルト値でした。

つまり、入力のたびに毎回サーバーへリクエストを送るのではなく、一定時間操作が止まってから、まとめて 1 回だけ送る仕組みになっています。

以下のように、form.setValidationTimeout()で変更が可能です。

<script setup lang="ts">
    import { useForm } from 'laravel-precognition-vue-inertia';

    const form = useForm('post', '/signup', {
        name: '',
        email: '',
        guests: 1,
    });

    form.setValidationTimeout(300); // ← ここ。formを作った直後に1回呼ぶ
</script>

実際のリクエスト・レスポンスを見てみた

ブラウザの通信を直接ログに取って、Precognition 用のヘッダーがどう使われているか確認しました。

操作Precognition-Validate-Only(送信)ステータスPrecognition-Success(応答)
名前だけ入力name204true
不正な形式のメールを入力email,name422(なし)
登録済みメールを入力email,name422(なし)
未登録メールに修正email,name204true
送信(通常の POST)-302-

Precognition-Validate-Only には、今回触ったフィールドだけでなく、それまでに触ったフィールドも累積してカンマ区切りで入ってくることが分かりました(name を触った後に email を触ると email,name になる)。

エラーがなければ本文なしの 204、エラーがあれば通常のバリデーションエラーと同じ形式で 422 が返ってきます。

先読み時点と実送信時点、どちらも本物の FormRequest を通るため、このログの通り unique などの DB を見るルールが 2 回分(先読み + 実送信)走ることになります。

頻繁に叩かれるフィールドに重いルールを付けるときは要注意です。

まとめ

とても便利だと思いました。

ユーザーの UX を考えると、送信を押す前にバリデーションを判定したほうが優しいので頑張ってフロントでもバックエンドと整合性をあわせて実装していましたが、

FormRequestの1箇所に書いたルールを使い回してフロントエンドのバリデーションも実装できるのでよかったです。

特に大幅な変更もいらないですし、既存のプロジェクトにも簡単に入れることができると思います。

axios を使ったちょっと前のプロジェクトにも入れられるので良いですね。

ただし今回試した限りでは、フロント用の公式パッケージが Inertia v3 に正式対応できておらず --legacy-peer-deps が必要だったので、最新の Laravel で使う場合は一応注意してみてください。

フィードバックのお願い
この記事のフィードバックがありましたらYoutubeの適当な動画にコメントしていただいたり、お問い合わせからご連絡ください。