Multi Page Applications (MPA’er) bliver mindre og mindre populære for hver dag der går. Berømte platforme som Facebook, Twitter, YouTube, Github og mange andre bruger allerede teknologi til single-page applications (SPA) i stedet.

Denne trendy teknologi gør det muligt for brugerne at bruge webapplikationer hurtigt og responsivt, fordi alt er klientside-renderet. Det kan dog være en plage for udviklere, der bygger server-side-renderede applikationer med frameworks som Laravel eller Django.

Heldigvis trådte Inertia.js til og kom til undsætning.

I denne artikel viser vi, hvordan du kan komme i gang med at bruge Inertia.js, og hvordan du kan bruge det sammen med Laravel, Vue.js og Tailwind CSS til at skabe en moderne blog-webapp. Vi vil også dele, hvordan du gør SPA’er mere SEO-venlige, samt et par andre tricks.

Hvis du lige er begyndt med Laravel, anbefaler vi, at du læser denne artikel først, så du er klar til at gå i gang.

Hvorfor SPA?

Før vi kan spørge, hvorfor vi skal bruge Inertia, skal vi først spørge: “Hvorfor SPA?”

Hvorfor skulle nogen foretrække klientside renderede applikationer frem for traditionelle server-side applikationer? Hvad ville tvinge en full-stack Laravel-udvikler til at sige farvel til bladkomponenter?

Det korte svar: Fordi hastighed og responsivitet er afgørende for ethvert succesfuldt brugerengagement.

I tilfælde af MPA’er sender browseren konstant forespørgsler til backend’en, som derefter udfører adskillige databaseforespørgsler. Når databasen og serveren har behandlet forespørgsler og leveret dem til browseren, bliver siden gengivet.

Men SPA’er er anderledes. Applikationen bringer alt det, brugeren har brug for, direkte til siden, så browseren ikke behøver at sende forespørgsler eller genindlæse siden for at gengive nye HTML-elementer.

På grund af denne enestående brugeroplevelse kræver mange store virksomheder, at deres websteder bliver til single-page-applikationer.

Når det er sagt, kan det være svært for Laravel-udviklere at skabe en single-page-applikation, fordi det vil kræve, at de begynder at bruge Vue.js eller React i stedet for bladskabeloner, hvilket resulterer i tab af mange Laravel-perler, der sparer tid og kræfter.

Men nu hvor vi har Inertia.js, er det hele ændret.

Hvorfor Inertia?

Hvis Laravel-udviklere skulle bygge web SPA’er med Vue før Inertia, skulle de opsætte API’er og returnere JSON-data med Laravel og derefter bruge noget som AXIOS til at hente dataene i Vue-komponenter. De ville også have brug for noget som Vue Router til at administrere ruter, hvilket ville betyde, at de ville miste Laravel-routing samt middlewares og controllere.

Inertia.js gør det derimod muligt for udviklere at bygge moderne enkeltsidede Vue-, React- og Svelte-apps ved hjælp af klassisk server-side-routing og controllere. Inertia blev designet til Laravel-, Ruby on Rails- og Django-udviklere for at give dem mulighed for at bygge apps uden at ændre deres kodningsteknikker med oprettelse af controllere, hentning af data fra en database og rendering af visninger

Takket være Inertia.js vil Laravel-udviklere føle sig hjemme.

Sådan fungerer Inertia

Hvis du kun bygger SPA med Laravel og Vue, får du en fuld JavaScript-side til din frontend, men det giver dig ikke en single-page app-oplevelse. Hvert klikket link vil få dit klientside framework til at genstarte ved næste sideindlæsning.

Det er her, at Inertia kommer ind i billedet.

Inertia er grundlæggende et routing-bibliotek på klientsiden. Det giver dig mulighed for at navigere mellem sider uden at skulle genindlæse hele siden. Dette opnås via komponenten <Link>, som er en letvægtsindpakning omkring et standard anchor-tag.

Når du klikker på et Inertia-link, opfanger Inertia klikket og omdirigerer dig i stedet til XHR. Browseren vil ikke genindlæse siden på denne måde, hvilket giver brugeren en fuld oplevelse med en enkelt side.

Kom godt i gang med Inertia

En simpel side med "Kinsta Blog" i et blåt banner øverst og en enkelt række af prøveartikelkort.
En eksempelside lavet med Inertia.js

For at forstå Inertia, og hvordan man integrerer det med Laravel, vil vi bygge en blog-webapp ved navn Kinsta Blog ved hjælp af den mest kraftfulde kombination, Laravel til backend, Vue.js til JavaScript-frontend og Tailwind CSS til styling.

Hvis du foretrækker at følge denne vejledning i et lokalt miljø, kan du bruge DevKinsta, et kraftfuldt værktøj til udviklere, designere og bureauer, der gør det muligt for dem at konstruere WordPress-webapps med en enkelt og flere sider. Heldigvis kan WordPress nemt integreres med Laravel ved hjælp af Corcel-pakken.

Forudsætninger

For at få mest muligt ud af denne vejledning bør du være bekendt med følgende:

  • Laravel-grundelementer (installation, database, databasemigrationer, Eloquent Models, controllere og routing)
  • Vue.js grundlæggende (installation, struktur og formularer)

Hvis du føler dig usikker, kan du tjekke disse fantastiske gratis og betalte Laravel-tutorials. Ellers, så lad os springe ud i det.

Trin 1: Installer Core Elements

For at fokusere på Inertia.js og komme til den sjove del med det samme, skal du sikre dig, at du har følgende opsætning klar til at gå:

  1. Frisk-installeret Laravel 9-projekt ved navn kinsta-blog
  2. Tailwind CSS CLI installeret i vores Laravel-projekt
  3. Billedfil “kinsta-logo.png.” Download og udpak Kinsta-logopakken fra https://kinsta.com/press/ og kopier kinsta-logo2.png til public/images-biblioteket som kinsta-logo.png
  4. To blade-komponenter i kinsta-blog/resources/views til visning af bloggens hjemmeside og en enkelt artikel på bloggen som vist nedenfor:”/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 lokal database ved navn kinsta_blog forbundet til vores projekt:”.env“:
    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=kinsta_blog
    DB_USERNAME=root
    DB_PASSWORD=
  6. Artikelmodel, migreringer og fabrikker: “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),
            ];
        }
    }

Det er alt, hvad vi behøver for at komme i gang! Lad os nu komme til sagen og introducere Inertia.js til vores projekt.

Trin 2: Installer Inertia

Inertia installationsprocessen er opdelt i to hovedfaser: server-side (Laravel) og klientside (VueJs).

Den officielle installationsvejledning i Inertia-dokumentationen er lidt forældet, fordi Laravel 9 nu bruger Vite som standard, men vi vil også gennemgå dette.

1. Server-side

Det første vi skal gøre er at installere Inertia server-side adaptere med nedenstående terminalkommando via Composer.

composer require inertiajs/inertia-laravel

Nu skal vi opsætte vores rodskabelon, som vil være en enkelt bladfil, der vil blive brugt til at indlæse dine CSS- og JS-filer, samt en Inertia-root, der vil blive brugt til at starte vores JavaScript-applikation.

Fordi vi bruger den nyeste version Laravel 9 v9.3.1, skal vi også aktivere Vite til at arbejde sin magi ved at inkludere det i vores tags i /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>

Bemærk, hvordan vi er i stand til at hente projektets titel dynamisk ved at tilføje inertia -attributten til <title> -tags.

Vi tilføjede også @vite -direktivet til head for at lade Vite kende stien til vores JavaScript-hovedfil, hvor vi oprettede vores app og importerede vores CSS. Vite er et værktøj, der hjælper med JavaScript- og CSS-udvikling ved at give udviklerne mulighed for at se frontend-ændringer uden at skulle opdatere siden under lokal udvikling.

Vores næste skridt bliver at oprette HandleInertiaRequests middleware og udgive den til vores projekt. Det kan vi gøre ved at affyre nedenstående terminalkommando i rodmappen for vores projekt:

php artisan inertia:middleware

Når dette er færdigt, skal du gå til “App/Http/Kernel.php” og registrere HandleInertiaRequests som det sidste element i dine web middlewares:

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

2. Client-Side

Dernæst skal vi installere vores Vue.js 3-afhængigheder på frontend-siden på samme måde som på server-siden:

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

Dernæst skal du trække Vue.js 3 ind:

npm install vue@next

Derefter skal du opdatere din primære JavaScript-fil for at initialisere Inertia.js med Vue.js 3, Vite og 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);
  },
});

I ovenstående kodestykke bruger vi Laravels plugin resolvePageComponent, og vi fortæller det, at det skal uploade vores komponenter fra mappen ./Pages/$name.vue. Dette skyldes, at vi senere i vores projekt vil gemme vores Inertia-komponenter i denne mappe, og dette plugin vil hjælpe os med automatisk at indlæse disse komponenter fra den korrekte mappe.

Det eneste, der er tilbage, er at installere vitejs/plugin-vue:

npm i @vitejs/plugin-vue

Og opdatere vite.config.js-filen:

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,
        },
      },
    }),
  ],
});

Det sidste trin er at installere vores afhængigheder og kompilere vores filer:

npm install

npm run dev

Og voilà! Du har fået dig en fungerende Laravel 9-applikation med Vue.js 3 og Vite. Nu skal vi se noget der sker i aktion!

Oprettelse af inertiesider

Kan du huske de to blade-filer (index og show) til visning af vores hjemmeside og en enkelt artikel?

Den eneste blade-fil vi skal bruge, mens vi bruger Inertia, er app.blade.php, som vi allerede brugte en gang, da vi installerede Inertia. Så hvad sker der med disse filer nu?

Vi vil omdanne disse filer fra blade-komponenter til Inertia.js-komponenter.

Hver side i din applikation har sin egen controller og JavaScript-komponent med Inertia. På den måde kan du kun få de data, der er nødvendige for den pågældende side, uden at bruge en API. Inertia-sider er intet andet end JavaScript-komponenter, i vores tilfælde er det Vue.js-komponenter. De har ikke noget særligt bemærkelsesværdigt ved dem. Så det vi vil gøre er at indpakke alt HTML-indhold mellem <template> tags og alt relateret til JavaScript vil blive indpakket med <script> tags.

Opret en mappe kaldet “Pages” og flyt dine filer dertil. Så vi vil have “index.blade.php” og “show.blade.php” i “./resources/js/Pages“. Derefter ændrer vi filformatet til “.vue” i stedet for “.blade.php”, mens vi gør det første bogstav i deres navne til store bogstaver og gør indholdet til en standard Vue.js-komponent. Vi udelukker <html>, <head> og <body> -taggene, fordi de allerede er inkluderet i hovedroden blade-komponenten.

“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>

Der er noget, der virkelig generer mig! Vi bliver ved med at kopiere og indsætte vores header og footer i hver komponent, hvilket ikke er en særlig god praksis. Lad os oprette et Inertia grundlæggende Layout til at gemme vores vedvarende komponenter.

Opret en mappe kaldet “Layouts” i “/resources/js” og inden for denne mappe opretter du en fil med navnet “KinstaLayout.vue”. Denne fil vil have vores header og footer og main med en <slot /> for at tillade alle komponenter, der er pakket med dette layout, at blive indlejret i det. Denne fil skal se således ud :

“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>

Vi vil derefter importere dette nye layout i vores sider og pakke alt HTML-indholdet ind i det. Vores komponenter skal se således ud:

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 og Inertia Render

Lad os først bruge “ArticleFactory“-filen, som vi har fra vores tutorial-startpunkt, og så nogle artikler i vores database.

“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();
    }
}

Derefter trykker du på nedenstående terminalkommando for at migrere dine tabeller og plante de falske data fra fabrikkerne:

php artisan migrate:fresh --seed

Dette vil oprette 10 falske artikler i databasen, som vi skal sende til vores visning ved hjælp af Laravel-routing. Nu hvor vi bruger Inertia til at rendere visninger, vil den måde vi plejede at skrive vores routes på ændre sig lidt. Lad os oprette vores første Laravel Inertia-route i “routes/web.php” og returnere forsidevisningen fra “/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');

Bemærk, at vi importerede Inertia og ikke brugte view() Laravel-hjælperen til at returnere visningen, men i stedet brugte Inertia::render. Inertia vil også som standard søge efter det filnavn, vi nævnte i vores route i Pages-mappen i “resources/js”.

Gå til Indeksfilen og indstil de hentede data som en prop og loop over dem med v-for for at vise resultaterne. Mellem scripttaggene defineres de overførte data som en prop. Det eneste Inertia skal vide er, hvilken type data du forventer, hvilket i vores tilfælde er et “articles”-objekt, der indeholder et array af artikler.

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

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

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

Bemærk, at det er nok kun at definere det som en prop uden at returnere det, fordi vi bruger setup -formatet for Vue.js 3-kompositions-API. Hvis vi bruger options API, skal vi returnere det.

Lad os lave en loop:

<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 (lad den køre, fordi vi bruger Vite) og php artisan serve for at starte laravel-udviklingsserveren og få adgang til vores websted, vil vi se den forventede side, der viser alle ti artikler i databasen.

Nu bruger vi Google Chromes Vue DevTools-udvidelse, som giver os mulighed for at debugge min applikation. Lad os vise dig, hvordan vores data bliver sendt til komponenten.

Chromes Vue DevTools-udvidelse, der viser en liste over Inertia-egenskaber for den åbne side.
Inspektion af inertiegenskaber.

“articles” overføres til komponenten som et prop-objekt, der indeholder et array af artikler; hver artikel i arrayet er også et object med egenskaber, der svarer til de data, den har erhvervet fra databasen. Det betyder, at alle data, som vi overfører fra Laravel til Inertia, vil blive behandlet som en prop.

Brug af Tailwind CSS med Inertia.js

Da Tailwind allerede er installeret i vores projekt i udgangspunktet, skal vi blot fortælle det at læse vores Inertia-komponenter. Dette kan gøres ved at redigere “tailwind.config.js” som følger:

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

Sørg derefter for, at vi har importeret vores CSS-fil i “resources/js/app.js“:

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

Og nu er vi klar til at style vores komponenter.

“resources/js/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>

Hvis du kigger i browseren, vil du bemærke, at Vite allerede har opdateret siden med Tailwind-magi.

Et rullende billede, der viser en fungerende version af "Kinsta Blog"-eksemplet fra tidligere.
Rendering af inertiegenskaber.

Inertia-links

Nu hvor vi har en fungerende hjemmeside, der kan vise alle artikler i databasen, skal vi oprette en anden rute til at vise de enkelte artikler. Lad os oprette en ny rute og indstille URL’en til et “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');

Vi importerede modellen “Article” og tilføjede en ny rute for at returnere Show.vue Inertia-komponenten. Vi udnyttede også Laravels route-modelbinding, som gør det muligt for Laravel automatisk at hente den artikel, vi henviser til.

Nu mangler vi blot en måde at besøge denne rute på ved at klikke på et link fra hjemmesiden uden at skulle genindlæse hele siden. Dette er muligt med Inertias magiske værktøj <Link>. Vi nævnte i indledningen, at Inertia bruger <Link> som en wrapper for et standard anchortag <a>, og at denne wrapper er beregnet til at gøre sidebesøg så problemfri som muligt. I Inertia kan <Link> -tagget opføre sig som et anchortag, der udfører <GET> -forespørgsler, men det kan også fungere som <button> og <form> på samme tid. Lad os se, hvordan vi kan anvende det på vores projekt.

I vores Index.vue vil vi importere <Link> fra Inertia og fjerne ankertagget <a> og erstatte det med Inertia <Link> -tags. Attributten href vil blive sat til den rute-URL, som vi tidligere har lavet til visning af artiklen:

<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>

Lad os style Show.vue med Tailwind for at få den til at se lidt mere pænere ud og klar til vores besøg. Og vi skal også lade den vide, at den skal forvente et “Artikel”-object og sætte det som en prop:

<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>

Når vi nu klikker på artiklens titel eller på “Læs mere”, vil vi på magisk vis blive transporteret til Show.vue uden at opdatere siden.

Eksempelsiden "Kinsta Blog" viser artikelkort med fungerende links.
Træghed links på plads.

I vores tilfælde bruger vi <Link> som et anchor tag, der sender en GET -forespørgsel til ruten og returnerer de nye data tilbage, men vi kan bruge <Link> til også POST, PUT, PATCH og DELETE

“routes/web.php“:

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

Laravel Inertia Tips og tricks du bør kende

Vi har nu en fungerende SPA bygget med Laravel, Inertia og Tailwind CSS. Men inertia kan hjælpe os med at opnå så meget mere. Det er tid til at tilegne sig nogle Inertia teknikker, der vil hjælpe både udviklere og applikationsbesøgende.

Generering af URL’er

Du har måske bemærket, at vi har tilføjet navne til vores Laravel routes uden at bruge det. Inertia giver os mulighed for at bruge vores navngivne routes i vores komponenter i stedet for manuelt at skrive den fulde route ned.

Vi kan opnå dette ved at installere Ziggy-pakken i vores projekt:

composer require tightenco/ziggy

Derefter går vi til “resources/js/app.js” og opdaterer den på denne måde:

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);
    },
});

Gå til “/resources/views/app.blade.php“, og opdater head med @routes-direktivet:

<!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>

…og opdater dine NPM-pakker ved at trykke på de to nedenstående terminalkommandoer:

npm install && npm run dev

Denne pakke giver os mulighed for at bruge navngivne ruter i vores Inertia-komponenter, så lad os gå til Index.vue og fjerne den gamle manuelle rute og erstatte den med routenavnet, mens vi videregiver dataene normalt, som hvis vi var i vores controller.

Vi vil erstatte dette:

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

…med dette:

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

Dette giver os nøjagtig den samme opførsel, som vi havde, men det er mere udviklervenligt og yderst nyttigt, når din rute forventer mange parametre.

Fremskridtsindikatorer

Dette er en af de fineste funktioner i Inertia.js; fordi SPA giver en interaktiv brugeroplevelse, ville det være en fantastisk tilføjelse til applikationen at have konstant feedback om, hvorvidt en anmodning er ved at blive indlæst. Dette kan opnås ved hjælp af et separat bibliotek, som Inertia tilbyder.

Biblioteket “@inertiajs/progress” er en wrapper omkring NProgress, der betinget viser indlæsningsindikatorerne i henhold til Inertia-hændelser. Du behøver ikke rigtig at vide, hvordan dette fungerer bag kulisserne, så lad os bare få det til at virke.

Vi kan installere dette bibliotek med nedenstående terminalkommando:

npm install @inertiajs/progress

Når det er installeret, skal vi importere det i “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 });

Dette vil vise en loading bar og en loading spinner i sort farve, men vi kan ændre farven sammen med andre nyttige muligheder, der kan findes på Inertia.js progress indicator dokumentation.

Den blå "Kinsta Blog"-header med den roterende indikator øverst til højre.
Inertia-progressionsindikatoren (øverst til højre).

Styring af rulning

I nogle tilfælde vil du måske gerne navigere til en ny side, mens du beholder den samme scrollposition. Måske har du brug for dette, hvis du giver brugerne mulighed for at efterlade kommentarer; dette vil sende en formular og indlæse den nye kommentar fra databasen til din komponent; du ønsker, at dette skal ske, uden at brugeren mister scrollpositionen. Inertia tager sig af dette for os.

I vores tilfælde, lad os anvende dette på vores <Link> tag i Index.vue. For at bevare scrollpositionen, mens vi omdirigerer til en anden side med Inertias <Link>, er det eneste, vi skal gøre, at tilføje preserve-scroll -attributten til <Link>:

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

SEO-tips

Siden SPA’ernes fødsel har folk været optaget af søgemaskineoptimering (SEO). Det er almindeligt kendt, at hvis du bruger SPA-tilgangen, vil søgemaskinerne have svært ved at crawle din webapplikation, fordi alt er klientside-renderet, hvilket resulterer i, at dit websted ikke vises øverst i søgeresultaterne; ikke desto mindre, hvordan kan det være, at de populære platforme som Facebook og Github nu er SPA’er og stadig klarer sig godt i SEO?

Nå, men det er ikke længere en mission: umuligt. Inertia tilbyder et par løsninger, der kan hjælpe din SPA med at blive SEO-venlig.

Inertia Vue SSR med Laravel og Vite

Søgemaskinerne leder altid efter HTML på din hjemmeside for at identificere indholdet; men hvis du ikke har HTML i dine URL’er, bliver dette job vanskeligere. Når du udvikler SPA’er, er JavaScript og JSON alt hvad du har på din side. Inertia introducerede en SSR-funktion (Server-Side Rendering), som du kan tilføje til dit program. Dette gør det muligt for din applikation at præ-rendere et indledende sidebesøg på serveren og derefter sende den renderede HTML til browseren. Dette giver brugerne mulighed for at se og interagere med dine sider, før de er fuldt indlæst, og det har også andre fordele, f.eks. at det forkorter den tid, det tager søgemaskinerne at indeksere dit websted.

For at opsummere, hvordan det fungerer, identificerer Inertia, om den kører på en Node.js-server, og renderer komponentnavne, egenskaber, URL og version af aktiver til HTML. Dette vil give brugeren og søgemaskinen praktisk talt alt, hvad din side har at byde på.

Men fordi vi har med Laravel at gøre, giver dette ikke meget mening, fordi Laravel er et PHP-framework og ikke kører på en Node.js-server. Derfor vil vi videresende forespørgslen til en Node.js-tjeneste, som vil rendere siden og returnere HTML. Dette vil gøre vores Laravel Vue-applikation SEO-venlig som standard.

Først skal vi installere Vue.js SSR npm-pakken Vue.js SSR:

npm install @vue/server-renderer

En anden nyttig Inertia “NPM”-pakke indeholder en simpel “HTTP”-server. Det anbefales kraftigt, at du installerer den:

npm install @inertiajs/server

Derefter tilføjer vi en ny fil med navnet ssr.js i “resources/js/”. Denne fil vil være meget lig den app.js-fil, som vi oprettede, da vi installerede Inertia, blot vil den blive udført i Node.js i stedet for i browseren:

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),
                });
        },
    })
);

Sørg for ikke at inkludere alt i ssr.js-filen, da den ikke vil være synlig for besøgende; denne fil er kun til brug for søgemaskiner og browsere til at vise dataene på din side, så inkluder kun det, der er vigtigt for dine data, eller kun det, der vil gøre dine data tilgængelige.

“Som standard vil Inertias SSR-server operere på port 13714. Du kan dog ændre dette ved at angive et andet argument til metoden createServer.” Inertia DOCss.

Inertia.js DOCs forklarer ikke, hvordan man integrerer Inertia SSR med Vite, men vi vil gennemgå dette nu. Gå til vite.config.js og indsæt nedenstående:

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,
                },
            },
        }),
    ],
});

Derefter skal du gå til package.json og ændre build-scriptet:

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

Hvis vi nu kører npm run build, vil Vite bygge vores SSR-pakke til produktion. For flere oplysninger om dette kan du besøge Inertia SSR DOCs og Vite SSR DOCs.

Titel og Meta

Da JavaScript-programmer gengives inden for dokumentets <body>, kan de ikke gengive markup til dokumentets <head>, da det ligger uden for deres anvendelsesområde. Inertia har en <Head> -komponent, som kan bruges til at indstille siden <title>, <meta> -tags og andre <head> -komponenter.

For at tilføje <head> -elementet til din side skal vi importere <Head> fra Inertia på samme måde som vi gjorde med <Link> -komponenten:

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

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

Vi kan også tilføje en global titel for alle sider, dette vil tilføje dit programnavn ved siden af titlen på alle sider. Det har vi allerede gjort i app.js-filen:

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

Det betyder, at hvis vi tilføjer <Head title="Home"> på vores applikations hjemmeside med en titel, vil det blive gengivet på følgende måde: <title>Home - My App</title>.

Overvågning af din app

Hastighed er en af de vigtigste faktorer i optimering af SEO-præstationer på dit websted. Hvis du bruger WordPress til dit websted, vil Kinsta APM af denne grund hjælpe dig med at overvåge og holde et vågent øje med din applikation i aktion. Det hjælper dig med at identificere WordPress-ydelsesproblemer og er gratis tilgængeligt på alle Kinsta-hostede websteder.

Opsummering

Inertia.js er en af de mest betydningsfulde teknologier, der findes; bland det med Laravel, og du har en moderne Single-Page-applikation bygget med PHP og JavaScript. Taylor Otwell, skaberen af Laravel, er så interesseret i Inertia, at Laravel har lanceret sine mest populære startpakker, Laravel Breeze og Jetstream, med Inertia og SSR-understøttelse.

Hvis du er Laravel-fan eller professionel udvikler, vil Inertia.js utvivlsomt fange dit øje. I denne tutorial har vi lavet en meget grundlæggende og ligetil blog på kun få minutter. Der er stadig masser at lære om inertia, og dette er måske kun den første af mange artikler og tutorials.

Hvad mere om Laravel vil du gerne have os til at udforske? Lad os vide det i kommentarfeltet nedenfor.

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.