메인 콘텐츠로 건너뛰기

Inertia.js란

프론트엔드를 React나 Vue로 만들고 싶다. 하지만 API를 별도로 설계 · 구현 · 관리하는 것은 피하고 싶다 — 이런 딜레마를 해결하는 것이 Inertia.js입니다. Inertia는 서버사이드 라우팅과 컨트롤러를 그대로 두고, 프론트엔드만을 React · Vue · Svelte로 작성할 수 있게 해 주는 “글루(접착제)“입니다. 프레임워크가 아니라, 기존의 Laravel과 JavaScript 프레임워크를 연결하는 어댑터 층으로 기능합니다.
현재 최신 버전은 Inertia v3 (2026년 3월 26일 릴리스)입니다. Laravel 13의 스타터 킷(React · Vue · Svelte)은 Inertia에 대응 완료되어 있습니다.

기존 SPA · MPA와의 차이

아키텍처특징과제
MPA (기존 Blade 앱)심플, Laravel과의 통합이 용이페이지 전환마다 풀 리로드
SPA (API + 프론트엔드 분리)높은 인터랙션성API 설계 · 인증 · 타입 정의의 이중 관리
Inertia (모던 모놀리스)SPA의 UX + 서버 측의 심플함독자적인 학습 비용 존재
Inertia를 사용하면 Laravel의 컨트롤러에서 직접 Vue 컴포넌트나 React 컴포넌트에 데이터를 전달할 수 있습니다. REST API를 정의할 필요가 없고, 페이지 전환도 브라우저의 풀 리로드가 아니라 XHR로 이루어지므로 SPA 같은 부드러운 조작감을 실현할 수 있습니다.

Laravel 스타터 킷과의 관계

laravel new로 프로젝트를 생성할 때 React · Vue · Svelte를 선택하면 Inertia가 자동으로 셋업됩니다.
laravel new my-app
# 대화식 프롬프트에서 React / Vue / Svelte를 선택하면 Inertia 구성이 된다
스타터 킷에서는 아래 구성이 자동으로 준비됩니다.
  • inertiajs/inertia-laravel (서버사이드 어댑터)
  • @inertiajs/react / @inertiajs/vue3 / @inertiajs/svelte (클라이언트 어댑터)
  • HandleInertiaRequests 미들웨어
  • 로그인 · 회원가입 · 패스워드 리셋 등의 인증 화면 (이미 Inertia 컴포넌트로 구현됨)
기존 프로젝트에 수동으로 Inertia를 도입하는 경우에는 서버사이드와 클라이언트사이드를 별도로 설치합니다. 자세한 내용은 공식 문서의 설치 절차를 참고하세요.

Inertia::render()의 기본

Laravel의 컨트롤러에서 Inertia 응답을 반환하려면 Inertia::render()를 사용합니다. 첫 번째 인자에 JavaScript 컴포넌트 이름, 두 번째 인자에 prop으로 전달할 데이터를 지정합니다.
use Inertia\Inertia;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('Posts/Index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }

    public function show(Post $post)
    {
        return Inertia::render('Posts/Show', [
            'post' => $post->only('id', 'title', 'content', 'created_at'),
            'author' => $post->user->only('id', 'name'),
        ]);
    }
}
Inertia::render() 대신 inertia() 헬퍼 함수도 사용할 수 있습니다. 어느 쪽을 사용할지는 팀의 스타일로 통일하세요.
컴포넌트 이름 'Posts/Index'는 파일 경로에 대응합니다. React라면 resources/js/Pages/Posts/Index.jsx, Vue라면 resources/js/Pages/Posts/Index.vue가 대응하는 컴포넌트입니다.

페이지 컴포넌트의 구조

Inertia의 페이지 컴포넌트는 일반적인 Vue/React 컴포넌트입니다. 컨트롤러에서 전달된 데이터를 prop으로 받을 수 있습니다.
React
// resources/js/Pages/Posts/Index.jsx
import { Link } from '@inertiajs/react'

export default function PostsIndex({ posts }) {
    return (
        <div>
            <h1>게시물 목록</h1>
            {posts.data.map(post => (
                <article key={post.id}>
                    <h2>
                        <Link href={`/posts/${post.id}`}>{post.title}</Link>
                    </h2>
                </article>
            ))}
        </div>
    )
}
Vue
<!-- resources/js/Pages/Posts/Index.vue -->
<script setup>
import { Link } from '@inertiajs/vue3'

defineProps({
    posts: Object,
})
</script>

<template>
    <div>
        <h1>게시물 목록</h1>
        <article v-for="post in posts.data" :key="post.id">
            <h2>
                <Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
            </h2>
        </article>
    </div>
</template>
<Link> 컴포넌트를 사용하면 페이지 전환이 XHR로 이루어져, 풀 페이지 리로드를 피할 수 있습니다. 일반적인 <a> 태그와 완전히 같은 방식으로 작성할 수 있지만, 뒤에서 Inertia가 페이지 컴포넌트만을 교체합니다.

공유 데이터 — HandleInertiaRequests 미들웨어

모든 페이지에서 공통으로 필요한 데이터(로그인 중인 사용자 정보 · 플래시 메시지 등)는 HandleInertiaRequests 미들웨어의 share() 메서드로 정의합니다.
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'appName' => config('app.name'),

            'auth' => [
                'user' => $request->user()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],

            'flash' => [
                'success' => $request->session()->get('success'),
                'error' => $request->session()->get('error'),
            ],
        ]);
    }
}
공유 데이터는 자동으로 모든 페이지의 prop에 병합됩니다.
React
// 레이아웃 컴포넌트에서 접근하는 예
import { usePage } from '@inertiajs/react'

export default function Layout({ children }) {
    const { auth, flash } = usePage().props

    return (
        <main>
            <header>
                {auth.user ? `로그인 중: ${auth.user.name}` : '게스트'}
            </header>
            {flash.success && <div className="alert-success">{flash.success}</div>}
            <article>{children}</article>
        </main>
    )
}
공유 데이터는 모든 요청에 포함되므로, 필요 최소한의 데이터로 좁힐 것을 권장합니다. fn()을 사용한 지연 평가로 하면, 실제로 필요한 요청에서만 평가됩니다.

폼 송신과 유효성 검증 오류 처리

Inertia에서의 폼 처리는 Laravel의 유효성 검증과 자연스럽게 통합됩니다. useForm() 헬퍼를 사용하면, 폼 상태 관리 · 송신 · 오류 표시를 심플하게 구현할 수 있습니다.

컨트롤러 측

class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', '게시물을 작성했습니다.');
    }
}
유효성 검증 오류가 발생하면 Laravel은 자동으로 폼 페이지로 리다이렉트하고, 오류 정보를 세션에 저장합니다. Inertia는 이를 자동으로 감지하여 errors prop으로 페이지에 전달합니다.

프론트엔드 측

React
// resources/js/Pages/Posts/Create.jsx
import { useForm } from '@inertiajs/react'

export default function PostsCreate() {
    const { data, setData, post, processing, errors } = useForm({
        title: '',
        content: '',
    })

    function handleSubmit(e) {
        e.preventDefault()
        post('/posts')
    }

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>제목</label>
                <input
                    value={data.title}
                    onChange={e => setData('title', e.target.value)}
                />
                {errors.title && <p className="error">{errors.title}</p>}
            </div>

            <div>
                <label>본문</label>
                <textarea
                    value={data.content}
                    onChange={e => setData('content', e.target.value)}
                />
                {errors.content && <p className="error">{errors.content}</p>}
            </div>

            <button type="submit" disabled={processing}>
                {processing ? '전송 중...' : '게시하기'}
            </button>
        </form>
    )
}
Vue
<!-- resources/js/Pages/Posts/Create.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
    title: '',
    content: '',
})

function submit() {
    form.post('/posts')
}
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <label>제목</label>
            <input v-model="form.title" />
            <p v-if="form.errors.title" class="error">{{ form.errors.title }}</p>
        </div>

        <div>
            <label>본문</label>
            <textarea v-model="form.content"></textarea>
            <p v-if="form.errors.content" class="error">{{ form.errors.content }}</p>
        </div>

        <button type="submit" :disabled="form.processing">
            {{ form.processing ? '전송 중...' : '게시하기' }}
        </button>
    </form>
</template>
유효성 검증 오류가 반환되었을 때, useForm()은 입력 내용을 유지한 채 오류를 표시합니다. 폼을 다시 입력하게 하는 수고 없이 사용자 경험이 향상됩니다.

적합한 유스케이스 · 부적합한 경우

Inertia가 적합한 경우

  • Laravel을 잘 알고 있는 팀이 SPA 같은 UI를 만들고 싶을 때
  • 인증 · 인가 · 유효성 검증을 Laravel 측에서 일원 관리하고 싶을 때
  • 관리 화면 · 사내 도구 등 SEO의 우선도가 낮은 앱
  • API를 별도로 설계 · 관리하는 비용을 피하고 싶은 프로젝트

부적합한 경우

  • 외부의 여러 클라이언트 (모바일 앱 등)가 동일한 API를 사용하는 경우
  • SEO가 매우 중요한 콘텐츠 사이트 (SSR로 대응 가능하지만 복잡해짐)
  • 마이크로프론트엔드 구성 등, 프론트엔드가 완전히 독립된 팀에서 개발되는 경우
Inertia는 서버사이드 렌더링(SSR)도 지원합니다. SEO가 필요한 페이지가 있는 경우에는 SSR 옵션을 검토하세요. Laravel의 스타터 킷에도 SSR 설정이 내장되어 있습니다.

Inertia v3의 주요 변경점

Inertia v3는 2026년 3월 26일에 릴리스되었습니다. v2로부터의 주요 변경점을 소개합니다.

Axios 폐지 — 경량 내장 XHR 클라이언트로

v3에서는 Axios가 폐지되고, 보다 경량인 내장 XHR 클라이언트로 대체되었습니다. 대부분의 애플리케이션에서 코드 변경은 불필요합니다. Axios의 인터셉터는 내장 인터셉터에 직접 마이그레이션할 수 있습니다. 계속해서 Axios를 사용하고 싶은 경우에는 Axios 어댑터 경유로 이용할 수 있습니다.

@inertiajs/vite 플러그인에 의한 SSR의 단순화

새로운 Vite 플러그인을 도입하면, 페이지 컴포넌트의 자동 해결과 SSR 설정이 대폭 간소화됩니다. 개발 중의 SSR은 npm run dev를 실행하는 것만으로 동작하도록 되어, 별도로 Node 서버를 기동할 필요가 없어졌습니다.
npm install @inertiajs/vite@^3.0

useHttp 훅 — 페이지 전환을 동반하지 않는 HTTP 요청

useHttp 훅을 사용하면 페이지 전환(Inertia visit)을 발생시키지 않고 서버로 HTTP 요청을 보낼 수 있습니다. 모달의 저장이나 백그라운드 처리 등, 현재 페이지를 유지한 채 통신하고 싶은 장면에서 유용합니다.

낙관적 UI 갱신 (Optimistic Updates)

useForm 및 라우터 레벨에서 낙관적 갱신이 지원되었습니다. 서버 응답을 기다리지 않고 UI를 즉시 갱신하고, 실패한 경우에는 자동으로 롤백합니다.

레이아웃 프롭 (Layout Props)

useLayoutProps 훅을 사용하면 페이지 컴포넌트에서 영구 레이아웃으로 데이터를 전달할 수 있게 되었습니다. 기존에는 공유 데이터나 페이지 prop에 의존할 수밖에 없었던 “특정 페이지의 레이아웃 상태 제어”를 심플하게 작성할 수 있습니다.

요구사항 변경

v3에서는 다음 최소 버전이 상향되었습니다.
항목v2v3
PHP8.1+8.2+
Laravel10+11+
React18+19+
Svelte4+5+

Inertia::lazy() 폐지

v2에서 비권장이 되었던 Inertia::lazy()가 v3에서 완전히 삭제되었습니다. 대신 Inertia::optional()을 사용합니다.
// v2 (비권장)
'users' => Inertia::lazy(fn () => User::all()),

// v3
'users' => Inertia::optional(fn () => User::all()),

이벤트 이름 변경

v2v3
invalidhttpException
exceptionnetworkError

future 옵션 폐지

v2에서 실험적 옵션으로 제공되던 future 설정 블록이 폐지되었습니다. 이 옵션들은 모두 기본으로 활성화되어 있습니다. createInertiaApp의 설정에서 future 블록을 삭제하세요.
v2로부터의 업그레이드 절차 상세는 공식 업그레이드 가이드를 참고하세요. v2는 2026년 9월 26일까지 버그 수정이, 2027년 3월 26일까지 보안 수정이 제공됩니다.

정리

Inertia.js는 “Laravel은 그대로 두고 프론트엔드를 React/Vue로 작성하고 싶다”는 현실적인 니즈에 부응하는 도구입니다. API를 설계하지 않고, 컨트롤러에서 직접 컴포넌트에 데이터를 전달할 수 있는 심플함이 최대 강점입니다. Laravel 13의 스타터 킷이 Inertia에 대응하고 있는 것에서도, Laravel 에코시스템에서 Inertia의 자리매김은 확립되어 있습니다. Livewire가 PHP만으로 동적 UI를 만드는 접근이라면, Inertia는 JavaScript 프레임워크의 파워를 서버 측의 심플함을 손상하지 않고 사용할 수 있는 접근입니다. 어느 쪽을 선택할지는 팀의 스킬 세트와 프로젝트의 요건에 따라 다르지만, “Laravel의 컨트롤러를 그대로 두고 React로 화면을 만들고 싶다”는 장면에서는 Inertia가 가장 자연스러운 선택입니다.

Inertia.js 공식 문서

Inertia v3의 전체 기능에 대해서는 공식 문서를 참고하세요.
마지막 수정일 2026년 7월 13일