Multi-page applicaties (MPA’s) worden met de dag minder populair. Bekende platforms als Facebook, Twitter, YouTube, Github, en vele andere gebruiken in plaats daarvan al geruime tijd single-page applicatie (SPA) technologie.

Met deze trendy technologie kunnen gebruikers zich snel en responsief bezighouden met webapplicaties, omdat alles aan de clientside wordt gerenderd. Het kan echter lastig zijn voor developers die serverside gerenderde applicaties bouwen met frameworks als Laravel of Django.

Gelukkig schoot Inertia.js te hulp.

In dit artikel laten we zien hoe je aan de slag kunt met Inertia.js en hoe je het kunt gebruiken met Laravel, Vue.js en Tailwind CSS om een moderne blogwebapp te maken. We delen ook hoe je SPA’s SEO vriendelijker kunt maken, en nog een paar andere trucs.

Als je net begint met Laravel, raden we je aan eerst dit artikel te lezen, zodat je er helemaal klaar voor bent om alles in dit artikel toe te passen.

Waarom SPA?

Voordat we ons kunnen afvragen waarom we Inertia moeten gebruiken, moeten we ons eerst überhaupt afvragen: “Waarom SPA?”

Waarom zou iemand een clientside gerenderde single-page applicatie verkiezen boven traditionele serverside applicaties? Wat zou een full-stack Laravel developer kunnen overhalen om afscheid te nemen van blade componenten?

Het korte antwoord: omdat snelheid en reactiesnelheid essentieel zijn voor elke succesvolle gebruikersbetrokkenheid.

In het geval van MPA’s stuurt de browser voortdurend verzoeken naar de backend, die vervolgens talloze databasequeries uitvoert. Nadat de database en de server de queries hebben verwerkt en aan de browser hebben geleverd, wordt de pagina gerenderd.

Maar SPA’s zijn anders. De applicatie brengt alles wat de gebruiker nodig heeft direct naar de pagina, zodat de browser geen queries hoeft te sturen of de pagina opnieuw hoeft te laden om nieuwe HTML elementen te renderen.

Vanwege deze unieke gebruikerservaring willen veel grote bedrijven dat hun websites single-page applicaties worden.

Dat gezegd hebbende, kan het maken van een single-page applicatie moeilijk zijn voor Laravel developers, omdat ze dan Vue.js of React moeten gaan gebruiken in plaats van blade templates, waardoor veel Laravel juweeltjes die tijd en moeite besparen verloren gaan.

Maar nu we Inertia.js hebben, is dat allemaal veranderd.

Waarom Inertia?

Als Laravel developers web SPA’s zouden bouwen met Vue vóór het tijdperk van Inertia, dan moesten ze API’s opzetten en JSON gegevens retourneren met Laravel, en dan iets als AXIOS gebruiken om de gegevens op te halen in Vue componenten. Ze zouden ook iets als Vue Router nodig hebben om routes te beheren, wat zou betekenen dat ze Laravel routing zouden verliezen, evenals middlewares en controllers.

Met Inertia.js daarentegen kunnen developers moderne single-page Vue, React en Svelte apps bouwen met behulp van klassieke serverside routing en controllers. Inertia is ontworpen voor developers van Laravel, Ruby on Rails en Django, zodat ze apps kunnen bouwen zonder hun coderingstechnieken te veranderen: controllers maken, gegevens ophalen uit een database en views renderen

Dankzij Inertia.js zullen Laravel developers zich meteen thuis voelen.

Zo werkt Inertia

Alleen SPA bouwen met Laravel en Vue geeft je een volledige JavaScript pagina voor je frontend, maar dit geeft je geen single-page appervaring. Elke aangeklikte link zorgt ervoor dat je clientside framework opnieuw opstart bij de volgende paginalading.

Dit is waar Inertia helpt.

Inertia is in feite een clientside routingbibliotheek. Hiermee kun je tussen pagina’s navigeren zonder de hele pagina opnieuw te hoeven laden. Dit gebeurt via het <Link> component, dat een lichtgewicht wrapper is rond een standaard anchortag.

Als je op een Inertia link klikt, onderschept Inertia de klik en stuurt je in plaats daarvan door naar XHR. De browser zal de pagina op deze manier niet herladen, waardoor de gebruiker een volledige single-page ervaring krijgt.

Aan de slag met Inertia

Een eenvoudige pagina met "Kinsta Blog" in een blauwe banner bovenaan en een enkele rij voorbeeldartikelkaarten.
Een voorbeeldpagina gemaakt met Inertia.js

Om Inertia te begrijpen en hoe het te integreren met Laravel, gaan we een blogwebapp bouwen genaamd Kinsta Blog met behulp van de meest krachtige combinatie, Laravel voor de backend, Vue.js voor de JavaScript frontend, en Tailwind CSS voor styling.

Als je deze tutorial liever in een lokale omgeving volgt, kun je DevKinsta gebruiken, een krachtige tool voor developers, ontwerpers en webbureaus waarmee ze WordPress webapps met één en meerdere pagina’s kunnen bouwen. Gelukkig kan WordPress gemakkelijk worden geïntegreerd met Laravel met behulp van het Corcel pakket.

Voorwaarden

Om het meeste uit deze tutorial te halen moet je bekend zijn met het volgende:

  • Laravel basics (installatie, database, databasemigraties, Eloquent Models, controllers en routing)
  • Vue.js basics (installatie, structuur en formulieren)

Als je je onzeker voelt, bekijk dan deze fantastische gratis en betaalde Laravel tutorials. Laten we beginnen.

Stap 1: Installeer de kernelementen

Om je te concentreren op Inertia.js en meteen naar het leuke gedeelte te gaan, moet je de volgende instellingen klaar hebben staan:

  1. Vers geïnstalleerd Laravel 9 project met de naam kinsta-blog
  2. Tailwind CSS CLI geïnstalleerd in ons Laravel project
  3. Afbeeldingsbestand “kinsta-logo.png”. Download & pak het Kinsta logo pakket uit van https://kinsta.com/press/, en kopieer kinsta-logo2.png naar public/images directory als kinsta-logo.png.
  4. Twee blade componenten in kinsta-blog/resources/views voor het bekijken van de homepage van de blog en een enkel artikel op de blog zoals hieronder te zien is:”/resources/views/index.blade.php“:
    <!DOCTYPE html>
    <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
    
        <title>Kinsta Blog</title>
      </head>
    
      <body>
        <header>
          <h1>Kinsta Blog</h1>
        </header>
    
        <main>
          <h2>Read our latest articles</h2>
    
          <section>
            <article>
              <div>
                <img src="/images/kinsta-logo.png" alt="Article thumbnail" />
              </div>
    
              <h3>Title for the blog</h3>
              <p>
                Lorem, ipsum dolor sit amet consectetur adipisicing elit. Illum rem
                itaque error vel perferendis aliquam numquam dignissimos, expedita
                perspiciatis consectetur!
              </p>
    
              <a href="#">Read more</a>
            </article>
          </section>
        </main>
    
        <footer>
          <h2>Join our Newsletter</h2>
    
          <input type="email" />
        </footer>
      </body>
    </html>

    “/resources/views/show.blade.php”:

    <!DOCTYPE html>
    <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
    
        <title>Kinsta Blog</title>
      </head>
    
      <body>
        <main>
          <article>
            <div>
              <img src="/images/kinsta-logo.png" alt="Article thumbnail" />
            </div>
    
            <h1>Title for the blog</h1>
    
            <p>Article content goes here</p>
          </article>
        </main>
    
        <footer>
          <h2>Join our Newsletter</h2>
    
          <input type="email" />
        </footer>
      </body>
    </html>
  5. MySQL lokale database genaamd kinsta_blog verbonden met ons project:”.env“:
    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=kinsta_blog
    DB_USERNAME=root
    DB_PASSWORD=
  6. Artikelmodel, migraties en factories:”app/Models/Article.php“:
    <?php
    
    namespace AppModels;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Illuminate\Database\Eloquent\Model;
    
    class Article extends Model
    {
        use HasFactory;
    
        protected $fillable = ['title', 'excerpt', 'body'];
    }

    “database/migrations/create_articles_table.php“:

    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
    
        public function up()
        {
            Schema::create('articles', function (Blueprint $table) {
                $table->id();
                $table->string('title');
                $table->text('excerpt');
                $table->text('body');
                $table->timestamps();
            });
        }
    
        public function down()
        {
            Schema::dropIfExists('articles');
        }
    };

    “database/factories/ArticleFactory.php“:

    <?php
    
    namespace DatabaseFactories;
    
    use Illuminate\Database\Eloquent\Factories\Factory;
    
    class ArticleFactory extends Factory
    {
    
        public function definition()
        {
            return [
                'title' => $this->faker->sentence(6),
                'excerpt' => $this->faker->paragraph(4),
                'body' => $this->faker->paragraph(15),
            ];
        }
    }

Dat is alles wat we nodig hebben om te beginnen! Laten we nu aan de slag gaan en Inertia.js introduceren in ons project.

Stap 2: Inertia installeren

Het installatieproces van Inertia is verdeeld in twee grote fasen: serverside (Laravel) en clientside (VueJs).

De officiële installatiehanleiding in de Inertia documentatie is een beetje verouderd omdat Laravel 9 nu standaard Vite gebruikt, maar ook dat zullen we doornemen.

1. Serverside

Het eerste wat we moeten doen is Inertia serverside adapters installeren met het onderstaande terminalcommando via Composer.

composer require inertiajs/inertia-laravel

Nu gaan we ons roottemplate instellen, dat een enkel blade bestand zal zijn en gebruikt zal worden om je CSS en JS bestanden te laden, en ook een Inertia root die gebruikt zal worden om onze JavaScript applicatie te starten.

Omdat we de meest recente versie Laravel 9 v9.3.1 gebruiken, moeten we ook Vite in staat stellen zijn werk te doen door het op te nemen in onze tags in /resources/views/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    
    <title inertia>{{ config('app.name', 'Laravel') }}</title>

    
    @vite('resources/js/app.js') @inertiaHead
  </head>

  <body class="font-sans antialiased">
    @inertia
  </body>
</html>

Let op hoe we de projecttitel dynamisch kunnen ophalen door het inertia attribuut toe te voegen aan de <title> tags.

We hebben ook de @vite directive toegevoegd aan de head om Vite het pad te laten weten van ons JavaScript hoofdbestand waar we onze app hebben gemaakt en onze CSS hebben geïmporteerd. Vite is een tool dat helpt bij de ontwikkeling van JavaScript en CSS door developers in staat te stellen wijzigingen aan de frontend te bekijken zonder de pagina te hoeven verversen tijdens lokale ontwikkeling.

Onze volgende stap is het maken van HandleInertiaRequests middleware en het publiceren ervan naar ons project. Dat kunnen we doen door het onderstaande terminalcommando in de hoofdmap van ons project af te vuren:

php artisan inertia:middleware

Zodra dit voltooid is, ga je naar “App/Http/Kernel.php” en registreer je HandleInertiaRequests als laatste item in je web middlewares:

'web' => [
    // ...
     App\Http\Middleware\HandleInertiaRequests::class,
],

2. Clientside

Vervolgens moeten we onze frontend Vue.js 3 dependencies op dezelfde manier installeren als aan de serverside:

npm install @inertiajs/inertia @inertiajs/inertia-vue3
// or
yarn add @inertiajs/inertia @inertiajs/inertia-vue3

Vervolgens moet je Vue.js 3 binnenhalen:

npm install vue@next

Werk dan je primaire JavaScript bestand bij om Inertia.js te initialiseren met Vue.js 3, Vite, en Laravel:

“resources/js/app.js“:

import "./bootstrap";
import "../css/app.css";

import { createApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/inertia-vue3";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";

createInertiaApp({
  title: (title) => `${title} - ${appName}`,
  resolve: (name) =>
    resolvePageComponent(
      `./Pages/${name}.vue`,
      import.meta.glob("./Pages/**/*.vue")
    ),
  setup({ el, app, props, plugin }) {
    return createApp({ render: () => h(app, props) })
      .use(plugin)
      .mount(el);
  },
});

In het bovenstaande codefragment gebruiken we Laravel’s plugin resolvePageComponent, en we vertellen het om onze componenten op te lossen vanuit de directory ./Pages/$name.vue. Dit is omdat we later in ons project onze Inertia componenten in deze map zullen opslaan, en deze plugin helpt ons bij het automatisch laden van die componenten uit de juiste map.

Wat overblijft is de installatie van vitejs/plugin-vue:

npm i @vitejs/plugin-vue

En het bestand vite.config.js bij te werken:

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

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

De laatste stap is het installeren van onze dependencies en het compileren van onze bestanden:

npm install

npm run dev

En voilà! Je hebt een werkende Laravel 9 applicatie met Vue.js 3 en Vite. Nu moeten we iets in actie zien!

Inertia pagina’s maken

Weet je nog die twee blade  bestanden (index en show) voor het bekijken van onze homepage en een los artikel?

Het enige blade bestand dat we nodig hebben bij het gebruik van Inertia is app.blade.php, dat we al een keer gebruikt hebben bij de installatie van Inertia. Dus wat gebeurt er nu met die bestanden?

We transformeren die bestanden van blade componenten in Inertia.js componenten.

Elke pagina in je applicatie heeft zijn eigen controller en JavaScript component met Inertia. Zo kun je alleen de gegevens verkrijgen die voor die pagina nodig zijn, zonder een API te gebruiken. Inertia pagina’s zijn niets meer dan JavaScript componenten, in ons geval zijn het Vue.js componenten. Ze hebben niets bijzonders in zich. Dus wat we gaan doen is alle HTML content wrappen tussen <template> tags en alles wat met JavaScript te maken heeft wordt gewrapped met <script> tags.

Maak een map genaamd “Pages” en verplaats je bestanden daarheen. Dus hebben we “index.blade.php” en “show.blade.php” in “./resources/js/Pages“. Dan veranderen we het bestandsformat in “.vue” in plaats van “.blade.php”, waar we de eerste letter van hun namen hoofdletter maken en de content veranderen in een standaard Vue.js component. We sluiten de tags <html>, <head>, en <body> uit, omdat die al zijn opgenomen in het hoofdcomponent van de blade.

“resources/js/Pages/Index.vue“:

<script setup>
  //
</script>

<template>
  <header>
    <h1>Kinsta Blog</h1>
  </header>

  <main>
    <h2>Read our latest articles</h2>

    <section>
      <article>
        <div>
          <img src="/images/kinsta-logo.png" alt="Article thumbnail" />
        </div>

        <h3>Title for the blog</h3>
        <p>
          Lorem, ipsum dolor sit amet consectetur adipisicing elit. Illum rem
          itaque error vel perferendis aliquam numquam dignissimos, expedita
          perspiciatis consectetur!
        </p>

        <a href="#">Read more</a>
      </article>
    </section>
  </main>

  <footer>
    <h2>Join our Newsletter</h2>

    <input type="email" />
  </footer>
</template>

“resources/js/Pages/Show.vue“:

<script setup>
  //
</script>

<template>
  <header>
    <h1>Welcome to Kinsta Blog</h1>
  </header>

  <main>
    <article>
      <h1>Title for the blog</h1>

      <p>Article content goes here</p>
    </article>
  </main>

  <footer>
    <h2>Join our Newsletter</h2>

    <input type="email" />
  </footer>
</template>

Iets zit me dwars! We blijven onze header en footer kopiëren en plakken in elk component, wat niet goed is. Laten we een Inertia basic Layout maken om onze persistente componenten in op te slaan.

Maak een map genaamd “Layouts” in “/resources/js” en maak in die map een bestand genaamd “KinstaLayout.vue”. Dit bestand zal onze header en footer bevatten en de main met een <slot /> zodat alle componenten die met deze layout zijn gewrapped erin kunnen worden ingesloten. Dit bestand moet er als volgt uitzien :

“resources/js/Layouts/KinstaLayout.vue“:

<script setup></script>

<template>
    <header>
    <h1>Kinsta Blog</h1>
  </header>

  <main>
        <slot />
  </main>

  <footer>
    <h2>Join our Newsletter</h2>

    <input type="email" />
  </footer>

</template>

Dan gaan we deze nieuwe layout importeren in onze pagina’s en alle HTML content ermee wrappen. Onze componenten zouden er zo uit moeten zien:

Index.vue:

<script setup>
import KinstaLayout from "../Layouts/KinstaLayout.vue";
</script>

<template>
  <KinstaLayout>
    <section>
      <h2>Read our latest articles</h2>
      <article>
        <div>
          <img src="/images/kinsta-logo.png" alt="Article thumbnail" />
        </div>

        <h3>Title for the blog</h3>
        <p>
          Lorem, ipsum dolor sit amet consectetur adipisicing elit. Illum rem
          itaque error vel perferendis aliquam numquam dignissimos, expedita
          perspiciatis consectetur!
        </p>

        <a href="#">Read more</a>
      </article>
    </section>
  </KinstaLayout>
 </template>

Show.vue:

<script setup>
 import KinstaLayout from "../Layouts/KinstaLayout.vue";
</script>

<template>
  <KinstaLayout>
    <article>
      <h1>Title for the blog</h1>

      <p>Article content goes here</p>
    </article>
  </KinstaLayout>
</template>

Laravel Routes en Inertia Render

Laten we eerst het “ArticleFactory” bestand gebruiken dat we hebben van ons tutorial startpunt en wat artikelen in onze database plaatsen.

“database/seeders/databaseSeeder.php“:

<?php

namespace Database\Seeders;

use App\Models\Article;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        Article::factory(10)->create();
    }
}

Druk nu op het onderstaande terminalcommando om je tabellen te migreren en de neppe gegevens van de factories te seeden:

php artisan migrate:fresh --seed

Dit zal 10 neppe artikelen in de database aanmaken, die we aan onze view moeten doorgeven met behulp van Laravel routing. Nu we Inertia gebruiken om views te renderen, zal de manier waarop we vroeger onze routes schreven enigszins veranderen. Laten we onze eerste Laravel Inertia route maken in “routes/web.php” en de homepage-weergave teruggeven vanuit “/resources/js/Pages/Index.vue“.

“routes/web.php“:

<?php

use App\Models\Article;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

Route::get('/', function () {
    return Inertia::render('Index', [
        'articles' => Article::latest()->get()
    ]);
})->name('home');

Let op dat we Inertia hebben geïmporteerd en niet de Laravel helper view() hebben gebruikt om de weergave terug te geven, maar in plaats daarvan Inertia::render. Inertia zal ook standaard zoeken naar de bestandsnaam die we in onze route hebben genoemd in de map Pages bij “resources/js”.

Ga naar het Indexbestand en stel de opgehaalde gegevens in als een prop en loop erover met v-for om de resultaten te tonen. Definieer tussen de scripttags de doorgegeven gegevens als een prop. Het enige wat Inertia moet weten is welk type gegevens je verwacht, wat in ons geval een ‘articles’ object is dat een array van artikelen bevat.

“resources/js/Pages/Index.vue“:

<script setup>
import KinstaLayout from "../Layouts/KinstaLayout.vue";

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

Let op dat het voldoende is om het alleen als prop te definiëren zonder het terug te geven, omdat we het setup format gebruiken voor Vue.js 3 composition API. Als we opties API gebruiken dan zouden we het moeten retourneren.

Laten we de loop maken:

<template>
  <KinstaLayout>
    <h2>Read our latest articles</h2>

    <section>
      // Looping over articles
      <article v-for="article in articles":key="article.id">
        <div>
          <img src="/images/kinsta-logo.png" alt="Article thumbnail" />
        </div>

        <h3>{{article.title}}</h3>
        <p>{{article.excerpt}}</p>

        <a href="#">Read more</a>
      </article>
    </section>
  </KinstaLayout>
</template>

npm run dev (laten lopen omdat we Vite gebruiken) en php artisan serve de laravel ontwikkelingsserver starten en onze website openen, dan zien we de verwachte pagina die alle tien artikelen in de database weergeeft.

Nu gebruiken we de extensie Vue DevTools van Google Chrome, waarmee we mijn applicatie kunnen debuggen. Laten we eens zien hoe onze gegevens worden doorgegeven aan de component.

Chrome's Vue DevTools extensie met een lijst met Inertia eigenschappen voor de open pagina.
Inertie eigenschappen inspecteren.

“articles” wordt aan de component doorgegeven als een prop object dat een array van artikelen bevat; elk artikel in de array is ook een object met properties die overeenkomen met de gegevens die het uit de database heeft verkregen. Dit betekent dat alle gegevens die we van Laravel naar Inertia overdragen, worden behandeld als een prop.

Tailwind CSS gebruiken met Inertia.js

Omdat Tailwind bij aanvang al in ons project is geïnstalleerd, hoeven we het alleen maar te vertellen dat het onze Inertia componenten moet lezen. Dit kan worden bereikt door “tailwind.config.js” als volgt te bewerken:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./storage/framework/views/*.php",
    "./resources/views/**/*.blade.php",
    "./resources/js/**/*.vue",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

Zorg er vervolgens voor dat we ons CSS bestand hebben geïmporteerd in “resources/js/app.js“:

import "../css/app.css";

En nu zijn we klaar om onze componenten te stylen.

“resources/js/Pages/Index.vue“:

<script setup>
import KinstaLayout from "../Layouts/KinstaLayout.vue";

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

<template>
 <KinstaLayout>
    <h2 class="text-2xl font-bold py-10">Read our latest articles</h2>

    <section class="space-y-5 border-b-2 pb-10">
      <article
        v-for="article in articles"
        :key="article.id"
        class="flex justify-center items-center shadow-md bg-white rounded-xl p-4 mx-auto max-w-3xl"
      >

         <img
            src="/images/kinsta-logo.png"
            class="w-32 h-32 rounded-xl object-cover"
            alt=""
         />

        <div class="flex flex-col text-left justify-between pl-3 space-y-5">
          <h3
            class="text-xl font-semibold text-indigo-600 hover:text-indigo-800"
          >
            <a href="#">{{ article.title }}</a>
          </h3>
          <p>
           {{ article.excerpt }}
          </p>
          <a
            href="#"
            class="text-indigo-600 hover:text-indigo-800 w-fit self-end font-semibold"
            >Read more</a
          >
        </div>
      </article>
    </section>
 </KinstaLayout>
</template>

“resources/js/Layouts/KinstaLayout.vue“:

<script setup></script>

<template>
    <header
        class="bg-gradient-to-r from-blue-700 via-indigo-700 to-blue-700 w-full text-center py-4"
    >
        <h1 class="text-white font-bold text-4xl">Kinsta Blog</h1>
    </header>

    <main class="container mx-auto text-center">
        <slot />
    </main>

    <footer
        class="bg-gradient-to-b from-transparent to-gray-300 w-full text-center mt-5 py-10 mx-auto"
    >
        <h2 class="font-bold text-xl pb-5">Join our Newsletter</h2>

        <input
            class="rounded-xl w-80 h-12 px-3 py-2 shadow-md"
            type="email"
            placeholder="Write your email.."
        />
    </footer>
</template>

Als je naar de browser kijkt, zie je dat Vite de pagina al heeft bijgewerkt met wat Tailwind magie.

Een scrollende afbeelding met een werkende versie van het "Kinsta Blog" voorbeeld van eerder.
Het renderen van Inertia eigenschappen.

Inertia links

Nu we een werkende homepage hebben die alle artikelen in de database kan weergeven, moeten we nog een route maken om individuele artikelen weer te geven. Laten we een nieuwe route maken en de URL instellen op een “id” wildcard:

“routes/web.php

<?php

use App\Models\Article;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

Route::get('/', function () {
    return Inertia::render('Index', [
        'articles' => Article::latest()->get()
    ]);
})->name('home');

Route::get('/posts/{article:id}', function (Article $article) {
    return Inertia::render('Show', [
        'article' => $article
    ]);
})->name('article.show');

We importeren het “Article” model en voegen een nieuwe route toe om de Show.vue Inertia component terug te geven. We maakten ook gebruik van Laravel’s route model binding, waardoor Laravel automatisch het artikel krijgt waarnaar we verwijzen.

Alles wat we nu nodig hebben is een manier om deze route te bezoeken door op een link van de homepage te klikken zonder de hele pagina opnieuw te hoeven laden. Dit is mogelijk met Inertia’s magische tool <Link>. We hebben in de inleiding vermeld dat Inertia <Link> gebruikt als wrapper voor een standaard anchortag <a>, en dat deze wrapper bedoeld is om paginabezoeken zo naadloos mogelijk te laten verlopen. In Inertia kan de <Link> tag zich gedragen als een anchortag die <GET> verzoeken uitvoert, maar hij kan ook tegelijkertijd fungeren als een <button> en een <form>. Laten we eens kijken hoe we dit in ons project kunnen toepassen.

In onze Index.vue zullen we <Link> van Inertia importeren, en de anchortags <a> verwijderen en vervangen door Inertia <Link> tags. Het href attribuut wordt ingesteld op de route URL die we eerder maakten voor het bekijken van het artikel:

<script setup>
import KinstaLayout from "../Layouts/KinstaLayout.vue";
import { Link } from "@inertiajs/inertia-vue3";

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

<template>
    <KinstaLayout>
        <section class="space-y-5 border-b-2 pb-10">
            <h2 class="text-2xl font-bold pt-10 mx-auto text-center">
                Read our latest articles
            </h2>

            <article
                v-for="article in articles"
                :key="article.id"
                class="flex justify-center items-center shadow-md bg-white rounded-xl p-4 mx-auto max-w-3xl"
            >
                <img
                    src="/images/kinsta-logo.png"
                    class="w-32 h-32 rounded-xl object-cover"
                    alt=""
                />

                <div
                    class="flex flex-col text-left justify-between pl-3 space-y-5"
                >
                    <h3
                        class="text-xl font-semibold text-indigo-600 hover:text-indigo-800"
                    >
                        <Link :href="'/posts/' + article.id">{{
                            article.title
                        }}</Link>
                    </h3>
                    <p>
                        {{ article.excerpt }}
                    </p>
                    <Link
                        :href="'/posts/' + article.id"
                        class="text-indigo-600 hover:text-indigo-800 w-fit self-end font-semibold"
                        >Read more
                    </Link>
                </div>
            </article>
        </section>
    </KinstaLayout>
</template>

Laten we Show.vue stylen met Tailwind om het er wat meer aangekleed en er mooi voor ons bezoek uit te laten zien. En ook moeten we het laten weten dat het een “Article” object moet verwachten en dat als prop instellen:

<script setup>
import KinstaLayout from "../Layouts/KinstaLayout.vue";

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

<template>
    <KinstaLayout>
        <article class="mx-auto mt-10 flex justify-center max-w-5xl border-b-2">
            <img
                src="/images/kinsta-logo.png"
                class="w-80 h-80 rounded-xl mx-auto py-5"
                alt=""
            />
            <div class="text-left flex flex-col pt-5 pb-10 px-10">
                <h1 class="text-xl font-semibold mb-10">{{ article.title }}</h1>
                <p>{{ article.body }}</p>
            </div>
        </article>
    </KinstaLayout>
</template>

Als we nu op de titel van het artikel of op “Read more” klikken, worden we op magische wijze naar Show.vue getransporteerd zonder de pagina te verversen.

De voorbeeld "Kinsta Blog" pagina met artikelkaarten met werkende links.
Inertia op hun plaats.

In ons geval gebruiken we <Link> als anchortag die een GET verzoek naar de route stuurt en de nieuwe gegevens terugstuurt, maar we kunnen <Link> gebruiken om ook POST, PUT, PATCH en DELETE

“routes/web.php“:

<Link href="/logout" method="post" as="button" type="button">Logout</Link>

Laravel Inertia tips en trucs die je moet weten

We hebben nu een werkende SPA gebouwd met Laravel, Inertia en Tailwind CSS. Maar Inertia kan ons helpen nog veel meer te bereiken. Het is tijd om ons enkele Inertia technieken eigen te maken die zowel de developers als de bezoekers van de applicatie zullen helpen.

URL’s genereren

Je hebt misschien gemerkt dat we namen hebben toegevoegd aan onze Laravel routes zonder die te gebruiken. Met Inertia kunnen we onze benoemde routes binnen onze componenten gebruiken in plaats van handmatig de volledige route op te schrijven.

We kunnen dit bereiken door het Ziggy pakket in ons project te installeren:

composer require tightenco/ziggy

Ga nu naar “resources/js/app.js” en werk het als volgt bij:

import "./bootstrap";
import "../css/app.css";

import { createApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/inertia-vue3";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
import { ZiggyVue } from "../../vendor/tightenco/ziggy/dist/vue.m";

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) =>
        resolvePageComponent(
            `./Pages/${name}.vue`,
            import.meta.glob("./Pages/**/*.vue")
        ),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .use(ZiggyVue, Ziggy)
            .mount(el);
    },
});

Ga naar “/resources/views/app.blade.php” en werk de head bij met @routes directive:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    
    <title inertia>{{ config('app.name', 'Laravel') }}</title>

    
    @routes
    @vite('resources/js/app.js')
    @inertiaHead
</head>

<body class="font-sans antialiased">
    @inertia
</body>

</html>

…en ververs je NPM pakketten door op de onderstaande twee terminalcommando’s te klikken:

npm install && npm run dev

Met dit pakket kunnen we routes met naam gebruiken binnen onze Inertia componenten, dus laten we naar Index.vue gaan en de oude handmatige route verwijderen en vervangen door de naam van de route, terwijl we de gegevens normaal doorgeven alsof we in onze controller zitten.

We zullen dit vervangen door:

<Link :href="'/posts/' + article.id">
   {{ article.title }}
</Link>

…door dit:

<Link :href="route('article.show', article.id)">
   {{ article.title }}
</Link>

Dit geeft ons precies hetzelfde gedrag dat we hadden, maar het is developervriendelijker en buitengewoon handig als je route veel parameters verwacht.

Voortgangsindicatoren

Dit is een van de leukste eigenschappen van Inertia.js; omdat SPA een interactieve gebruikerservaring biedt, zou het een fantastische toevoeging zijn om voortdurend feedback te hebben of een verzoek aan het laden is. Dit kan worden bereikt door een aparte bibliotheek die Inertia aanbiedt.

De “@inertiajs/progress” bibliotheek is een wrapper rond NProgress die conditioneel de laadindicatoren toont volgens Inertia events. Je hoeft niet echt te weten hoe dit achter de schermen werkt, dus laten we het gewoon aan de praat krijgen.

We kunnen deze bibliotheek installeren met het onderstaande terminal commando:

npm install @inertiajs/progress

Als hij eenmaal geïnstalleerd is, moeten we hem importeren in “resources/js/app.js

import "./bootstrap";
import "../css/app.css";

import { createApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/inertia-vue3";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
import { ZiggyVue } from "../../vendor/tightenco/ziggy/dist/vue.m";
import { InertiaProgress } from "@inertiajs/progress";

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) =>
        resolvePageComponent(
            `./Pages/${name}.vue`,
            import.meta.glob("./Pages/**/*.vue")
        ),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .use(ZiggyVue, Ziggy)
            .mount(el);
    },
});

InertiaProgress.init({ color: "#000000", showSpinner: true });

Dit toont een laadbalk en een laadspinner in zwarte kleur, maar we kunnen de kleur veranderen samen met andere nuttige opties die te vinden zijn in de documentatie over de voortgangsindicator van Inertia.js.

De blauwe "Kinsta Blog" header met de draaiende indicator rechtsboven.
De Inertia voortgangsindicator (rechtsboven).

Scrollbeheer

In sommige gevallen wil je naar een nieuwe pagina navigeren terwijl je dezelfde scrollpositie behoudt. Misschien heb je dit nodig als je gebruikers toestaat comments achter te laten; dit zal een formulier verzenden en het nieuwe comment uit de database in je component laden; je wilt dat dit gebeurt zonder dat de gebruiker zijn scrollpositie verliest. Inertia regelt dit voor ons.

In ons geval passen we dit toe op onze <Link> tag in Index.vue. Om de scrollpositie te behouden bij het doorsturen naar een andere pagina met Inertia’s <Link> hoeven we alleen maar het attribuut preserve-scroll toe te voegen aan de <Link>:

<Link :href="route('article.show', article.id)" preserve-scroll>
  {{ article.title }}
</Link>

SEO tips

Sinds het ontstaan van SPA’s maken mensen zich zorgen over search engine optimization (SEO). Het is algemeen bekend dat als je de SPA aanpak gebruikt, zoekmachines het moeilijk zullen hebben om je webapplicatie te crawlen omdat alles clientside wordt gerenderd, met als gevolg dat je website niet bovenaan de zoekresultaten verschijnt; hoe komt het toch dat die populaire platforms als Facebook en Github nu SPA’s zijn en nog steeds goed presteren in SEO?

Nou, dit is geen mission impossible meer. Inertia biedt een paar oplossingen om je SPA SEO vriendelijk te maken.

Inertia Vue SSR met Laravel en Vite

Zoekmachines zoeken altijd naar HTML op je website om de content te identificeren; maar als je geen HTML in je URL’s hebt, wordt deze taak moeilijker. Als je SPA’s ontwikkelt, heb je op je pagina alleen JavaScript en JSON. Inertia introduceerde een Server-Side Rendering (SSR) feature die je aan je applicatie kunt toevoegen. Hiermee kan je app een eerste pagina op de server renderen en vervolgens de gerenderde HTML naar de browser sturen. Hierdoor kunnen gebruikers je pagina’s zien en ermee werken voordat ze volledig geladen zijn, en het heeft ook andere voordelen, zoals het verkorten van de tijd die zoekmachines nodig hebben om je site te indexeren.

Om samen te vatten hoe het werkt, zal Inertia identificeren of het draait op een Node.js server en de componentnamen, properties, URL, en assets versie renderen naar HTML. Dit geeft de gebruiker en de zoekmachine praktisch alles wat je pagina te bieden heeft.

Omdat we te maken hebben met Laravel heeft dit echter weinig zin, omdat Laravel een PHP framework is en niet draait op een Node.js server. Daarom sturen we het verzoek door naar een Node.js service, die de pagina rendert en HTML teruggeeft. Dit zal onze Laravel Vue applicatie standaard SEO vriendelijk maken.

Eerst moeten we het Vue.js SSR npm pakket installeren:

npm install @vue/server-renderer

Een ander nuttig Inertia “NPM” pakket biedt een eenvoudige “HTTP” server. Het wordt sterk aanbevolen om het te installeren:

npm install @inertiajs/server

Vervolgens voegen we in “resources/js/” een nieuw bestand toe met de naam ssr.js. Dit bestand lijkt sterk op het app.js bestand dat we maakten bij de installatie van Inertia, alleen wordt het uitgevoerd in Node.js in plaats van in de browser:

import { createSSRApp, h } from "vue";
import { renderToString } from "@vue/server-renderer";
import { createInertiaApp } from "@inertiajs/inertia-vue3";
import createServer from "@inertiajs/server";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
import { ZiggyVue } from "../../vendor/tightenco/ziggy/dist/vue.m";

const appName = "Laravel";

createServer((page) =>
    createInertiaApp({
        page,
        render: renderToString,
        title: (title) => `${title} - ${appName}`,
        resolve: (name) =>
            resolvePageComponent(
                `./Pages/${name}.vue`,
                import.meta.glob("./Pages/**/*.vue")
            ),
        setup({ app, props, plugin }) {
            return createSSRApp({ render: () => h(app, props) })
                .use(plugin)
                .use(ZiggyVue, {
                    ...page.props.ziggy,
                    location: new URL(page.props.ziggy.location),
                });
        },
    })
);

Zorg ervoor dat je niet alles in het ssr.js bestand opneemt, omdat het niet zichtbaar zal zijn voor bezoekers; dit bestand is alleen bedoeld voor zoekmachines en browsers om de gegevens binnen je pagina te tonen, dus neem alleen op wat belangrijk is voor je gegevens of alleen wat je gegevens beschikbaar maakt.

“Standaard werkt de SSR server van Inertia op poort 13714. Je kunt dit echter veranderen door een tweede argument te geven aan de createServer methode.” Inertia DOCss.

De Inertia.js DOCs leggen niet uit hoe je Inertia SSR met Vite kunt integreren, maar we zullen dit nu doornemen. Ga naar vite.config.js en plak het onderstaande:

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

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

Ga vervolgens naar package.json en verander het build script:

"build": "vite build && vite build --ssr"

Als we nu npm run build uitvoeren, zal Vite onze SSR bundel bouwen voor productie. Voor meer informatie hierover kun je Inertia SSR DOCs en Vite SSR DOCs bezoeken.

Title en meta

Omdat JavaScript applicaties worden weergegeven binnen de <body> van het document, kunnen ze geen markup weergeven op de <head> van het document, omdat dit buiten hun bereik valt. Inertia heeft een <Head> component die gebruikt kan worden om de pagina <title>, <meta> tags en andere <head> componenten in te stellen.

Om een <head> element aan je pagina toe te voegen, moeten we <Head> importeren uit Inertia, net zoals we deden met de <Link> component:

import { Head } from '@inertiajs/inertia-vue3'

<Head>
  <title>Kinsta Blog</title>
  <meta name="description" content="Kinsta blog for developers">
</Head>

We kunnen ook een globale titel toevoegen voor alle pagina’s, dit zal je applicatienaam toevoegen naast de titel op elke pagina. Dat hebben we al gedaan in het app.js bestand:

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    //
});

Wat betekent dat als we in de homepage van onze applicatie <Head title="Home"> toevoegen met een titel, dit als volgt wordt weergegeven: <title>Home - My App</title>.

Het monitoren van je app

Snelheid is een van de belangrijkste factoren bij het optimaliseren van de SEO prestaties op je website. Als je WordPress gebruikt voor je website, helpt Kinsta APM je om die reden bij het bewaken en goed in de gaten houden van je applicatie in actie. Het helpt je WordPress prestatieproblemen te identificeren en is gratis beschikbaar op alle door Kinsta gehoste sites.

Samenvatting

Inertia.js is een van de belangrijkste technologieën die je tegenwoordig kan gebruiken; combineer het met Laravel en je hebt een moderne Single-Page applicatie gebouwd met PHP en JavaScript. Taylor Otwell, de bedenker van Laravel, is zo geïnteresseerd in Inertia dat Laravel zijn populairste starterkits, Laravel Breeze en Jetstream, heeft gelanceerd met ondersteuning voor Inertia en SSR.

Als je een Laravel fan bent of een professionele developer, Inertia.js zal ongetwijfeld je aandacht trekken. In deze tutorial hebben we in een paar minuten een heel eenvoudige en simpele blog gemaakt. Er valt nog genoeg te leren over Inertia, en dit kan zomaar pas de eerste van vele artikelen en tutorials zijn.

Wat zou je nog meer over Laravel willen ontdekken? Laat het ons weten in de commentsectie hieronder.

Mostafa Said

I’m Mostafa, a full-stack developer with a knack for all things Laravel, Inertia, and JavaScript frameworks. When I'm not coding, you can find me sharing my knowledge through tutorials, diving into hackathons (and winning a few), and spreading the love for tech by teaching what I've learned.