Inertia.js + Vue 3 w PHP - Nowoczesne SPA bez API

29.07.2025

Inertia.js + Vue 3 w PHP - Nowoczesne SPA bez API

Czym jest Inertia.js?

Inertia.js to protokół pozwalający na budowanie Single Page Applications (SPA) w sposób, który eliminuje konieczność pisania dedykowanego API. Zamiast tradycyjnego podejścia REST/GraphQL API + frontend framework, Inertia działa jako "most" między serwerem PHP a frontendem Vue/React/Svelte.

Dlaczego Inertia.js?

Kluczowe zalety

  • Brak potrzeby API - serwer zwraca dane bezpośrednio do komponentów Vue
  • Natywne routowanie - używasz routingu PHP zamiast Vue Router
  • Server-side rendering opcjonalnie - łatwa migracja do SSR
  • Zachowanie stanu - automatyczne zarządzanie historią przeglądarki
  • Płynne przejścia - doświadczenie jak w natywnej SPA

Jak działa protokół Inertia.js?

Protokół Inertia definiuje sposób komunikacji między serwerem a klientem poprzez specjalne nagłówki HTTP i format danych JSON.

Żądania Inertia

Gdy użytkownik klika w link lub przesyła formularz, Inertia wysyła żądanie AJAX z nagłówkiem X-Inertia: true. Serwer rozpoznaje to i zwraca dane w specjalnym formacie JSON zamiast pełnego HTML.

Odpowiedzi serwera

{
  "component": "Home/Index",
  "props": {
    "user": {
      "id": 1,
      "name": "Jan Kowalski",
      "email": "jan@example.com"
    },
    "posts": [
      {"id": 1, "title": "Pierwszy post"},
      {"id": 2, "title": "Drugi post"}
    ]
  },
  "url": "/dashboard",
  "version": "1.0"
}

Pierwsze załadowanie

Przy pierwszym wejściu na stronę (bez nagłówka X-Inertia), serwer zwraca pełny HTML z zainicjalizowaną aplikacją Vue. Dane strony są przekazywane jako JSON w znaczniku data-page.

Integracja z Laravelem vs własny framework

Laravel posiada gotową paczkę do Inertia, która znacznie upraszcza integrację. W kursie PHP 8.4 implementujemy własną, pełną integrację Inertia.js, co pozwala zrozumieć dokładnie jak działa protokół pod spodem.

Laravel (gotowe rozwiązanie)

<?php
// W kontrolerze Laravel
use Inertia\Inertia;

class DashboardController extends Controller
{
    public function index()
    {
        return Inertia::render('Dashboard/Index', [
            'stats' => [
                'users' => User::count(),
                'posts' => Post::count(),
            ]
        ]);
    }
}

Własny framework (nasza implementacja)

<?php
// Implementujemy od zera
class Inertia
{
    private array $sharedData = [];

    public function share(string|array $key, mixed $value = null): void
    {
        if (is_array($key)) {
            $this->sharedData = array_merge($this->sharedData, $key);
        } else {
            $this->sharedData[$key] = $value;
        }
    }

    public function render(string $component, array $props = []): ResponseInterface
    {
        $page = [
            'component' => $component,
            'props' => array_merge($this->sharedData, $props),
            'url' => $_SERVER['REQUEST_URI'],
            'version' => '1.0',
        ];

        return $this->responseFactory->createResponse($page);
    }
}

Implementacja ResponseFactory

ResponseFactory decyduje, czy zwrócić odpowiedź JSON (dla żądań Inertia) czy pełny HTML (dla pierwszego załadowania).

<?php
class ResponseFactory
{
    public function createResponse(array $page): ResponseInterface
    {
        // Sprawdź czy to żądanie Inertia
        if ($this->isInertiaRequest()) {
            return $this->createInertiaResponse($page);
        }

        // Pierwsze załadowanie - zwróć pełny HTML
        return $this->createHtmlResponse($page);
    }

    private function isInertiaRequest(): bool
    {
        return isset($_SERVER['HTTP_X_INERTIA'])
            && $_SERVER['HTTP_X_INERTIA'] === 'true';
    }

    private function createInertiaResponse(array $page): ResponseInterface
    {
        return new JsonResponse($page, 200, [
            'X-Inertia' => 'true',
            'Vary' => 'Accept',
        ]);
    }

    private function createHtmlResponse(array $page): ResponseInterface
    {
        $html = view('app', [
            'page' => json_encode($page)
        ]);

        return new Response($html);
    }
}

InertiaMiddleware - obsługa protokołu

Middleware Inertia odpowiada za przygotowanie środowiska dla żądań Inertia, w tym ustawienie głównego widoku i danych współdzielonych.

<?php
class InertiaMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // Ustaw root view dla Inertia
        app(Inertia::class)->withRootView('inertia');

        // Współdziel dane dostępne w każdym komponencie
        app(Inertia::class)->share([
            'auth' => [
                'user' => auth()->user(),
            ],
            'flash' => [
                'success' => session('success'),
                'error' => session('error'),
            ],
        ]);

        return $handler->handle($request);
    }
}

Root View - punkt wejścia aplikacji

Root view to główny widok Blade, który ładuje aplikację Vue i inicjalizuje Inertia.

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    @inertiaHead
    @vite('resources/js/app.js')
</head>
<body>
    @inertia
</body>
</html>

Dyrektywy Blade dla Inertia

@inertiaHead - nagłówki strony

<?php
class InertiaHeadDirective extends Directive
{
    public function compile(string $template): string
    {
        return str_replace(
            '@inertiaHead',
            '<?php echo app(Inertia::class)->head(); ?>',
            $template
        );
    }
}

@inertia - kontener aplikacji

<?php
class InertiaDirective extends Directive
{
    public function compile(string $template): string
    {
        $replacement = '<div id="app" data-page=\'<?php echo json_encode($page); ?>\'></div>';

        return str_replace('@inertia', $replacement, $template);
    }
}

Integracja z Vite

Vite to nowoczesny bundler oferujący szybkie ładowanie modułów i efektywną kompilację. Dyrektywa @vite w Blade automatycznie ładuje zasoby w odpowiednim trybie (development/production).

Konfiguracja vite.config.js

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

export default defineConfig({
    plugins: [vue()],
    build: {
        manifest: true,
        outDir: 'public/build',
        rollupOptions: {
            input: 'resources/js/app.js',
        },
    },
    server: {
        host: 'localhost',
        port: 5173,
        hmr: {
            host: 'localhost',
        },
    },
});

Konfiguracja Vue 3 z Inertia

app.js - inicjalizacja aplikacji

import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'

createInertiaApp({
  resolve: name => {
    const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
    return pages[`./Pages/${name}.vue`]
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

Formularze w Inertia - useForm

Hook useForm zapewnia wygodne API do obsługi formularzy z automatyczną walidacją błędów.

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
 name: '',
 email: '',
 password: '',
})

const submit = () => {
 form.post('/register', {
  onSuccess: () => form.reset(),
  onError: () => {
   // Błędy automatycznie dostępne w form.errors
  }
 })
}
</script>

<template>
 <form @submit.prevent="submit">
  <div >
   <label>Imię</label>
   <input v-model="form.name" type="text" >
   <div v-if="form.errors.name" >
    {{ form.errors.name }}
   </div>
  </div>

  <div >
   <label>Email</label>
   <input v-model="form.email" type="email" >
   <div v-if="form.errors.email" >
    {{ form.errors.email }}
   </div>
  </div>

  <div >
   <label>Hasło</label>
   <input v-model="form.password" type="password" >
   <div v-if="form.errors.password" >
    {{ form.errors.password }}
   </div>
  </div>

  <button type="submit" :disabled="form.processing" >
   <span v-if="form.processing">Przetwarzanie...</span>
   <span v-else>Zarejestruj</span>
  </button>
 </form>
</template>

Vue 3 Composition API - przegląd

W kursie wykorzystujemy nowoczesne Composition API z składnią <script setup>, która jest prostsza i bardziej zwięzła niż Options API.

Reactive state

<script setup>
import { ref, reactive, computed } from 'vue'

// ref - dla prostych wartości
const count = ref(0)
const increment = () => count.value++

// reactive - dla obiektów
const state = reactive({
  user: null,
  loading: false
})

// computed - obliczane wartości
const doubleCount = computed(() => count.value * 2)
</script>

Najlepsze praktyki przy korzystaniu z Inertia.js + vue3

  • Używaj shared data dla danych dostępnych globalnie (user, flash messages)
  • Lazy load komponentów Vue dla lepszej wydajności
  • Wykorzystuj TypeScript dla type safety
  • Stosuj Composition API + script setup dla czystszego kodu
  • Pamiętaj o responsywności - testuj na mobile

🎓 Zbuduj SPA z Inertia.js od zera!

W kursie PHP 8.4 implementujesz pełną integrację Inertia.js z własnym frameworkiem PHP - od ResponseFactory i middleware, przez dyrektywy Blade (@inertia, @vite), aż po zaawansowane komponenty Vue 3 z Composition API. Poznasz protokół Inertia od podszewki i zbudujesz nowoczesną SPA bez pisania API.

Kup pełny kurs PHP 8.4 🚀 Pobierz darmowy fragment 📥

Zobacz powiązane artykuły