Precognition
업데이트됨번역일: 2026년 6월 21일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 6월 20일
- 번역 갱신
- 2026년 6월 21일
Precognition
소개
Laravel Precognition은 아직 실행하지 않은 HTTP 요청의 결과를 미리 예측할 수 있는 기능입니다. 가장 대표적인 활용 사례는 프론트엔드 JavaScript 앱에서의 실시간 유효성 검사입니다. 백엔드의 유효성 검사 규칙을 프론트엔드에 중복으로 작성할 필요 없이, 백엔드 규칙을 그대로 재사용할 수 있습니다.
Laravel이 "사전 인식(precognitive)" 요청을 수신하면, 라우트에 등록된 모든 미들웨어를 실행하고 컨트롤러 의존성을 해결하며 폼 리퀘스트 유효성 검사까지 수행합니다. 단, 컨트롤러 메서드 자체는 실행하지 않습니다.
NOTE
Inertia 2.3부터는 Precognition 지원이 내장되어 있습니다. 자세한 내용은 Inertia Forms 문서를 참고하세요. 이전 버전의 Inertia를 사용하는 경우 Precognition 0.x가 필요합니다.
실시간 유효성 검사
Precognition의 동작 원리를 간단히 정리하면 다음과 같습니다.
Vue 사용하기
Precognition을 사용하면 Vue 프론트엔드 앱에 유효성 검사 규칙을 중복으로 작성하지 않고도 실시간 유효성 검사 경험을 제공할 수 있습니다. 사용자 생성 폼을 예로 들어 설명합니다.
먼저, 라우트에 HandlePrecognitiveRequests 미들웨어를 등록합니다. 그리고 유효성 검사 규칙을 담을 폼 리퀘스트를 생성합니다.
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (StoreUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);다음으로 Vue용 Precognition 프론트엔드 패키지를 설치합니다.
npm install laravel-precognition-vue패키지가 설치되면 useForm 함수로 폼 객체를 생성합니다. HTTP 메서드(post), 대상 URL(/users), 초기 폼 데이터를 인수로 전달합니다.
각 입력 필드의 change 이벤트에서 validate 메서드를 호출하면 실시간 유효성 검사가 활성화됩니다.
<script setup>
import { useForm } from 'laravel-precognition-vue';
const form = useForm('post', '/users', {
name: '',
email: '',
});
const submit = () => form.submit();
</script>
<template>
<form @submit.prevent="submit">
<label for="name">이름</label>
<input
id="name"
v-model="form.name"
@change="form.validate('name')"
/>
<div v-if="form.invalid('name')">
{{ form.errors.name }}
</div>
<label for="email">이메일</label>
<input
id="email"
type="email"
v-model="form.email"
@change="form.validate('email')"
/>
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>
<button :disabled="form.processing">
사용자 생성
</button>
</form>
</template>사용자가 폼을 입력하면 Precognition이 폼 리퀘스트의 유효성 검사 규칙을 기반으로 실시간 검사 결과를 제공합니다. 입력값이 변경될 때마다 디바운스(debounce)된 Precognition 요청이 Laravel 애플리케이션으로 전송됩니다. 디바운스 시간은 setValidationTimeout 함수로 조정할 수 있습니다.
form.setValidationTimeout(3000);유효성 검사 요청이 진행 중일 때는 validating 속성이 true가 됩니다.
<div v-if="form.validating">
검사 중...
</div>유효성 검사 요청이나 폼 제출 중 발생한 오류는 자동으로 errors 객체에 반영됩니다.
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>폼에 오류가 하나라도 있는지 확인하려면 hasErrors 속성을 사용합니다.
<div v-if="form.hasErrors">
<!-- ... -->
</div>특정 입력 필드가 유효성 검사를 통과했는지 여부는 valid 및 invalid 함수로 확인할 수 있습니다.
<span v-if="form.valid('email')">
✅
</span>
<span v-else-if="form.invalid('email')">
❌
</span>WARNING
입력 필드의 유효/무효 상태는 값이 변경되고 유효성 검사 응답을 수신한 이후에만 표시됩니다.
Precognition으로 일부 필드만 검사할 경우, 특정 필드의 오류를 수동으로 제거하고 싶을 때가 있습니다. 이때는 forgetError 함수를 사용합니다.
<input
id="avatar"
type="file"
@change="(e) => {
form.avatar = e.target.files[0]
form.forgetError('avatar')
}"
>사용자가 아직 상호작용하지 않은 필드도 검사해야 하는 경우가 있습니다. 예를 들어 "단계별 마법사(wizard)" UI에서 다음 단계로 이동하기 전에 현재 화면의 모든 필드를 검사하고 싶을 때가 이에 해당합니다. 이 경우 validate 메서드에 only 키로 검사할 필드명 배열을 전달하고, onSuccess 또는 onValidationError 콜백으로 결과를 처리합니다.
<button
type="button"
@click="form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})"
>다음 단계</button>폼 제출 후 응답에 따라 추가 처리를 하고 싶다면, submit 함수가 반환하는 Axios 프로미스를 활용하세요.
const submit = () => form.submit()
.then(response => {
form.reset();
alert('사용자가 생성되었습니다.');
})
.catch(error => {
alert('오류가 발생했습니다.');
});폼 제출 요청이 진행 중인지는 processing 속성으로 확인합니다.
<button :disabled="form.processing">
제출
</button>React 사용하기
React 애플리케이션에서도 동일하게 실시간 유효성 검사를 제공할 수 있습니다.
먼저, 라우트에 HandlePrecognitiveRequests 미들웨어를 등록하고 폼 리퀘스트를 생성합니다.
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (StoreUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);React용 Precognition 패키지를 설치합니다.
npm install laravel-precognition-reactuseForm 함수로 폼 객체를 생성합니다. React에서는 change 이벤트에서 setData로 데이터를 갱신하고, blur 이벤트에서 validate를 호출하는 패턴을 사용합니다.
import { useForm } from 'laravel-precognition-react';
export default function Form() {
const form = useForm('post', '/users', {
name: '',
email: '',
});
const submit = (e) => {
e.preventDefault();
form.submit();
};
return (
<form onSubmit={submit}>
<label htmlFor="name">이름</label>
<input
id="name"
value={form.data.name}
onChange={(e) => form.setData('name', e.target.value)}
onBlur={() => form.validate('name')}
/>
{form.invalid('name') && <div>{form.errors.name}</div>}
<label htmlFor="email">이메일</label>
<input
id="email"
value={form.data.email}
onChange={(e) => form.setData('email', e.target.value)}
onBlur={() => form.validate('email')}
/>
{form.invalid('email') && <div>{form.errors.email}</div>}
<button disabled={form.processing}>
사용자 생성
</button>
</form>
);
};디바운스 시간 설정:
form.setValidationTimeout(3000);유효성 검사 진행 중 표시:
{form.validating && <div>검사 중...</div>}오류 표시:
{form.invalid('email') && <div>{form.errors.email}</div>}전체 오류 여부 확인:
{form.hasErrors && <div><!-- ... --></div>}유효/무효 상태 표시:
{form.valid('email') && <span>✅</span>}
{form.invalid('email') && <span>❌</span>}WARNING
입력 필드의 유효/무효 상태는 값이 변경되고 유효성 검사 응답을 수신한 이후에만 표시됩니다.
특정 필드 오류를 수동으로 제거할 때는 forgetError 함수를 사용합니다.
<input
id="avatar"
type="file"
onChange={(e) => {
form.setData('avatar', e.target.files[0]);
form.forgetError('avatar');
}}
>단계별 마법사 UI 등에서 특정 필드들을 일괄 검사할 때는 only 옵션을 사용합니다.
<button
type="button"
onClick={() => form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})}
>다음 단계</button>폼 제출 후 응답 처리:
const submit = (e) => {
e.preventDefault();
form.submit()
.then(response => {
form.reset();
alert('사용자가 생성되었습니다.');
})
.catch(error => {
alert('오류가 발생했습니다.');
});
};제출 진행 중 버튼 비활성화:
<button disabled={form.processing}>
제출
</button>Alpine과 Blade 사용하기
Alpine.js와 Blade를 사용하는 환경에서도 Precognition으로 실시간 유효성 검사를 구현할 수 있습니다.
먼저, 라우트에 미들웨어를 등록하고 폼 리퀘스트를 생성합니다.
use App\Http\Requests\CreateUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (CreateUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);Alpine용 Precognition 패키지를 설치합니다.
npm install laravel-precognition-alpineresources/js/app.js 파일에서 Alpine에 Precognition 플러그인을 등록합니다.
import Alpine from 'alpinejs';
import Precognition from 'laravel-precognition-alpine';
window.Alpine = Alpine;
Alpine.plugin(Precognition);
Alpine.start();패키지 등록 후, Alpine의 $form 매직을 사용해 폼 객체를 생성합니다. change 이벤트에서 validate를 호출하면 실시간 유효성 검사가 동작합니다.
<form x-data="{
form: $form('post', '/register', {
name: '',
email: '',
}),
}">
@csrf
<label for="name">이름</label>
<input
id="name"
name="name"
x-model="form.name"
@change="form.validate('name')"
/>
<template x-if="form.invalid('name')">
<div x-text="form.errors.name"></div>
</template>
<label for="email">이메일</label>
<input
id="email"
name="email"
x-model="form.email"
@change="form.validate('email')"
/>
<template x-if="form.invalid('email')">
<div x-text="form.errors.email"></div>
</template>
<button :disabled="form.processing">
사용자 생성
</button>
</form>디바운스 시간 설정:
form.setValidationTimeout(3000);유효성 검사 진행 중 표시:
<template x-if="form.validating">
<div>검사 중...</div>
</template>오류 표시:
<template x-if="form.invalid('email')">
<div x-text="form.errors.email"></div>
</template>전체 오류 여부 확인:
<template x-if="form.hasErrors">
<div><!-- ... --></div>
</template>유효/무효 상태 표시:
<template x-if="form.valid('email')">
<span>✅</span>
</template>
<template x-if="form.invalid('email')">
<span>❌</span>
</template>WARNING
입력 필드의 유효/무효 상태는 값이 변경되고 유효성 검사 응답을 수신한 이후에만 표시됩니다.
단계별 마법사 UI에서 특정 필드들을 일괄 검사할 때:
<button
type="button"
@click="form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})"
>다음 단계</button>제출 진행 중 버튼 비활성화:
<button :disabled="form.processing">
제출
</button>이전 입력값 복원
위의 Alpine 예제는 전통적인 서버 사이드 폼 제출 방식을 사용합니다. 따라서 서버 사이드 제출 후 리다이렉트 시 "이전(old)" 입력값과 유효성 검사 오류를 폼에 다시 채워 넣어야 합니다.
<form x-data="{
form: $form('post', '/register', {
name: '{{ old('name') }}',
email: '{{ old('email') }}',
}).setErrors({{ Js::from($errors->messages()) }}),
}">XHR 방식으로 폼을 제출하려면 submit 함수가 반환하는 Axios 프로미스를 활용합니다.
<form
x-data="{
form: $form('post', '/register', {
name: '',
email: '',
}),
submit() {
this.form.submit()
.then(response => {
this.form.reset();
alert('사용자가 생성되었습니다.')
})
.catch(error => {
alert('오류가 발생했습니다.');
});
},
}"
@submit.prevent="submit"
>Axios 설정하기
Precognition 라이브러리는 내부적으로 Axios HTTP 클라이언트를 사용합니다. 필요에 따라 Axios 인스턴스를 커스터마이징할 수 있습니다. 예를 들어 laravel-precognition-vue를 사용하는 경우, resources/js/app.js에서 모든 요청에 공통 헤더를 추가할 수 있습니다.
import { client } from 'laravel-precognition-vue';
client.axios().defaults.headers.common['Authorization'] = authToken;이미 애플리케이션에서 설정된 Axios 인스턴스가 있다면, Precognition이 해당 인스턴스를 사용하도록 지정할 수도 있습니다.
import Axios from 'axios';
import { client } from 'laravel-precognition-vue';
window.axios = Axios.create()
window.axios.defaults.headers.common['Authorization'] = authToken;
client.use(window.axios)배열 유효성 검사
배열이나 중첩 객체 내부의 필드를 검사할 때는 와일드카드(*)를 사용할 수 있습니다. * 하나는 경로의 단일 세그먼트에 해당합니다.
// 배열의 모든 사용자 이메일 검사
form.validate('users.*.email');
// profile 객체의 모든 필드 검사
form.validate('profile.*');
// 모든 사용자의 모든 필드 검사
form.validate('users.*.*');유효성 검사 규칙 커스터마이징
isPrecognitive 메서드를 사용하면 Precognition 요청 시 적용되는 유효성 검사 규칙을 별도로 지정할 수 있습니다.
예를 들어, 사용자 생성 폼에서 비밀번호가 "유출된 비밀번호 목록에 없는지" 확인하는 검사는 최종 제출 시에만 수행하고 싶을 수 있습니다. Precognition 요청 시에는 필수 여부와 최소 길이만 검사하도록 아래와 같이 구성합니다.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;
class StoreUserRequest extends FormRequest
{
/**
* 요청에 적용할 유효성 검사 규칙을 반환합니다.
*
* @return array
*/
protected function rules()
{
return [
'password' => [
'required',
$this->isPrecognitive()
? Password::min(8)
: Password::min(8)->uncompromised(),
],
// ...
];
}
}파일 업로드 처리
기본적으로 Precognition은 사전 인식 요청 시 파일을 업로드하거나 검사하지 않습니다. 대용량 파일이 불필요하게 여러 번 업로드되는 것을 방지하기 위함입니다.
따라서 파일 필드에 required 규칙은 실제 폼 제출 시에만 적용되도록 폼 리퀘스트를 아래와 같이 설정해야 합니다.
/**
* 요청에 적용할 유효성 검사 규칙을 반환합니다.
*
* @return array
*/
protected function rules()
{
return [
'avatar' => [
...$this->isPrecognitive() ? [] : ['required'],
'image',
'mimes:jpg,png',
'dimensions:ratio=3/2',
],
// ...
];
}모든 유효성 검사 요청에 파일을 포함하고 싶다면, 클라이언트 측 폼 인스턴스에서 validateFiles 함수를 호출합니다.
form.validateFiles();부작용(Side-Effect) 관리
HandlePrecognitiveRequests 미들웨어를 라우트에 추가할 때는, 다른 미들웨어에서 발생할 수 있는 부작용 중 Precognition 요청 시에는 건너뛰어야 할 항목이 없는지 검토해야 합니다.
예를 들어, 사용자의 총 "상호작용" 횟수를 증가시키는 미들웨어가 있다고 가정합니다. 이때 Precognition 요청은 실제 상호작용으로 집계되지 않도록 isPrecognitive 메서드로 조건 처리할 수 있습니다.
<?php
namespace App\Http\Middleware;
use App\Facades\Interaction;
use Closure;
use Illuminate\Http\Request;
class InteractionMiddleware
{
/**
* 요청을 처리합니다.
*/
public function handle(Request $request, Closure $next): mixed
{
if (! $request->isPrecognitive()) {
Interaction::incrementFor($request->user());
}
return $next($request);
}
}테스트
테스트에서 Precognition 요청을 보내려면 Laravel의 TestCase에서 제공하는 withPrecognition 헬퍼를 사용합니다. 이 헬퍼는 요청에 Precognition 헤더를 자동으로 추가합니다.
Precognition 요청이 유효성 검사 오류 없이 성공했는지 확인하려면 응답 객체의 assertSuccessfulPrecognition 메서드를 사용합니다.
Pest
it('Precognition으로 회원가입 폼을 유효성 검사한다', function () {
$response = $this->withPrecognition()
->post('/register', [
'name' => 'Taylor Otwell',
]);
$response->assertSuccessfulPrecognition();
// Precognition 요청이므로 실제 사용자는 생성되지 않아야 함
expect(User::count())->toBe(0);
});PHPUnit
public function test_it_validates_registration_form_with_precognition()
{
$response = $this->withPrecognition()
->post('/register', [
'name' => 'Taylor Otwell',
]);
$response->assertSuccessfulPrecognition();
// Precognition 요청이므로 실제 사용자는 생성되지 않아야 함
$this->assertSame(0, User::count());
}