MPA: er (Multi-page applications) blir allt mindre populära för varje dag som går. Berömda plattformar som Facebook, Twitter, YouTube, Github och många andra använder redan SPA-teknik (single-page application) i stället.

Denna trendiga teknik gör det möjligt för användarna att nyttja webbapplikationer snabbt och responsivt eftersom allt är renderat på klientsidan. Detta kan dock vara en plåga för utvecklare som bygger serverbaserade renderade applikationer med ramverk som Laravel eller Django.

Lyckligtvis så har Inertia.js kommit till undsättning.

I den här artikeln så visar vi hur du kan komma igång med Inertia.js och hur du kan använda detta tillsammans med Laravel, Vue.js och Tailwind CSS för att skapa en modern bloggwebbapplikation. Vi kommer även att dela med oss av hur man gör SPA: er mer SEO-vänliga, samt några andra knep.

Om du precis har börjat med Laravel så rekommenderar vi att du läser den här artikeln först så att du är redo att köra igång.

Varför SPA?

Innan vi kan fråga oss varför vi ska använda Inertia så måste vi fråga: ”Varför SPA?”

Varför skulle någon föredra klient-side renderade applikationer framför traditionella server-side applikationer? Vad kan få en fullstack Laravel-utvecklare att säga adjö till blade-komponenter?

Det korta svaret: för att snabbhet och responsivitet är avgörande för ett framgångsrikt användarbeteende.

När det gäller MPA: er så skickar webbläsaren ständigt förfrågningar till backend, som sedan utför många databasfrågor. Efter att databasen och servern har bearbetat förfrågningarna och levererat dem till webbläsaren, renderas sidan.

Men SPA fungerar annorlunda. Applikationen tar med sig allt som användaren behöver direkt till sidan, vilket gör att webbläsaren inte behöver skicka frågor eller ladda om sidan för att återge nya HTML-element.

Tack vare denna unika användarupplevelse så kräver många stora företag att deras webbplatser ska bli ensidiga applikationer.

Med detta sagt så kan det vara svårt för Laravel-utvecklare att skapa en enkelsidesapplikation eftersom det skulle kräva att de börjar använda Vue.js eller React i stället för blade-mallar, vilket leder till att många Laravel-juveler som sparar tid och arbete går förlorade.

Tack vare Inertia.js så har dock allt detta förändrats.

Varför Inertia?

Om Laravel-utvecklare skulle bygga webb-SPA: er med Vue istället för Inertia så skulle de behöva konfigurera API: er och returnera JSON-data med Laravel och sedan använda något som AXIOS för att hämta data i Vue-komponenter. De skulle även behöva något som Vue Router för att hantera rutter, vilket skulle innebära att de skulle förlora Laravel-routning samt middlewares och kontrollerare.

Inertia.js gör det å andra sidan möjligt för utvecklare att bygga moderna enkelsidiga Vue-, React- och Svelte-appar med hjälp av klassisk server-side routing och kontrollerare. Inertia utformades för Laravel-, Ruby on Rails- och Django-utvecklare för att de skulle kunna bygga appar utan att ändra sina kodningstekniker för att skapa styrenheter. De skulle även kunna hämta data från en databas och rendera vyer.

Tack vare Inertia.js så kommer Laravel-utvecklare att känna sig som hemma.

Hur Inertia fungerar

Att bygga SPA med endast Laravel och Vue ger dig en fullständig JavaScript-sida för din frontend, men inte en enkelsidig app-upplevelse. Varje klickad länk kommer att få ditt ramverk på klientsidan att starta om vid nästa sidladdning.

Det är här som Inertia kommer in i bilden.

Inertia är i princip ett routingbibliotek för klientsidan. Det gör det möjligt att navigera mellan sidor utan att det krävs en omladdning av hela sidan. Detta sker via komponenten <Link>, som är en lättviktig omslagsform kring en standard ankartagg.

När du klickar på en Inertia-länk så fångar Inertia upp klicket och omdirigerar dig till XHR i stället. Webbläsaren kommer inte att ladda om sidan, vilket ger användaren en fullständig upplevelse av en enda sida.

Komma igång med Inertia

En exempelsida gjord med Inertia.js
En exempelsida gjord med Inertia.js

För att förstå Inertia och hur man integrerar det med Laravel så ska vi bygga en bloggwebbapplikation vid namn Kinsta Blog med hjälp av den mest kraftfulla kombinationen. Vi ska nyttja Laravel för backend, Vue.js för JavaScript-frontend och Tailwind CSS för styling.

Om du föredrar att följa den här handledningen i en lokal miljö så kan du använda DevKinsta, ett kraftfullt verktyg för utvecklare, designers och agenturer som gör det möjligt för dem att konstruera WordPress-webbappar med en eller flera sidor. Lyckligtvis så kan WordPress enkelt integreras med Laravel med hjälp av Corcel-paketet.

Förutsättningar

För att få ut så mycket som möjligt av den här handledningen så bör du känna till följande:

  • Laravel-grunderna (installation, databas, databasmigreringar, Eloquent Models, kontrollerare och routing)
  • Vue.js-grunderna (installation, struktur och formulär)

Om du känner dig osäker så kan du kolla in dessa fantastiska kostnadsfria och betalda handledningarna om Laravel. Annars så kan vi köra igång.

Steg 1: Installera Core Elements

För att fokusera på Inertia.js och komma till den roliga delen direkt, se till att du har följande installation redo:

  1. Ett nyinstallerat Laravel 9-projekt med namnet kinsta-blog
  2. Tailwind CSS CLI installerat i vårt Laravel-projekt
  3. Bildfil ”kinsta-logo.png.” Ladda ner och packa upp Kinsta’s logotyp-paket från https://kinsta.com/press/ och kopiera kinsta-logo2.png till public/images directory som kinsta-logo.png.
  4. Två blade-komponenter i kinsta-blog/resources/views för visning av bloggens startsida och en enskild artikel på bloggen som visas nedan:”/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 databas med namnet kinsta_blog ansluten till vårt projekt: ”.env”:
    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=kinsta_blog
    DB_USERNAME=root
    DB_PASSWORD=
  6. Artikelmodell, migreringar och Factories: ”app/Modeller/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 är allt som vi behöver för att komma igång! Nu ska vi komma till saken och introducera Inertia.js i vårt projekt.

Steg 2: Installera Inertia

Installationsprocessen för Inertia är uppdelad i två huvudfaser: serversidan (Laravel) och klientsidan (VueJs).

Den officiella installationsguiden i Inertia-dokumentationen är lite föråldrad eftersom Laravel 9 nu använder Vite som standard, men vi kommer att gå igenom även den.

1. Server-sidan

Det första som vi behöver göra är att installera Inertia server-side adapters med nedanstående terminalkommando via Composer.

composer require inertiajs/inertia-laravel

Nu ställer vi in vår rotmall, som kommer att vara en enda blade-fil. Den kommer att användas för att ladda dina CSS- och JS-filer, samt en Inertia-root som kommer att användas för att starta vår JavaScript-applikation.

Eftersom vi använder den senaste versionen Laravel 9 v9.3.1 så måste vi även låta Vite utföra sin magi genom att inkludera den i våra taggar 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>

Lägg märke till hur vi kan hämta projekttiteln dynamiskt genom att lägga till attributet inertia i <title> -taggarna.

Vi har även lagt till @vite -direktivet i sidhuvudet för att Vite ska få veta sökvägen till vår JavaScript-huvudfil där vi skapade vår app och importerade vårt CSS. Vite är ett verktyg som hjälper till med JavaScript- och CSS-utveckling genom att låta utvecklare se ändringar på frontend utan att behöva uppdatera sidan under lokal utveckling.

Vårt nästa steg blir att skapa HandleInertiaRequests-middleware och publicera den i vårt projekt. Vi kan göra detta genom att avfyra nedanstående terminalkommando i rotkatalogen för vårt projekt:

php artisan inertia:middleware

När detta är klart, gå till ”App/Http/Kernel.php” och registrera HandleInertiaRequests som den sista punkten i din webb- middleware:

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

2. Klientsidan

Därefter så måste vi installera våra Vue.js 3-beroenden för frontend på samma sätt som på serversidan:

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

Efter detta så måste du hämta in Vue.js 3:

npm install vue@next

Uppdatera sedan din primära JavaScript-fil för att initiera Inertia.js med Vue.js 3, Vite och 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 ovanstående kodutdrag så använder vi Laravel’s plugin resolvePageComponent, och vi säger åt det att lösa upp våra komponenter från katalogen ./Pages/$name.vue. Detta beror på att vi kommer att spara våra Inertia-komponenter i den här katalogen senare i vårt projekt, och detta plugin hjälper oss att ladda dessa komponenter från rätt katalog automatiskt.

Allt som återstår är att installera vitejs/plugin-vue:

npm i @vitejs/plugin-vue

Och uppdatera 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 sista steget är att installera våra beroenden och kompilera våra filer:

npm install

npm run dev

Och voilà! Du har en fungerande Laravel 9-applikation med Vue.js 3 och Vite. Nu behöver vi se vad som händer i praktiken!

Skapa tröghetssidor

Minns du de där två blade-filerna (index och show) för att visa vår hemsida och en enskild artikel?

Den enda blade-filen som vi behöver när vi använder Inertia är app.blade.php, som vi använde en gång redan när vi installerade Inertia. Så vad händer med dessa filer nu?

Vi kommer att omvandla dessa filer från blade-komponenter till Inertia.js-komponenter.

Varje sida i din applikation har sin egen kontrollerare och JavaScript-komponent med Inertia. På så sätt så kan du bara få fram de data som krävs för den sidan, utan att använda ett API. Inertia-sidor är inget annat än JavaScript-komponenter, i vårt fall så är det Vue.js-komponenter. Det är inget anmärkningsvärt med dem. Så vad vi kommer att göra är att omsluta allt HTML-innehåll med <template> -taggar och allt som har med JavaScript att göra kommer att omslutas med <script> -taggar.

Skapa en mapp som heter ”Pages” och flytta dina filer dit. Så vi kommer att ha ”index.blade.php” och ”show.blade.php” i ”./resources/js/Pages”. Vi ändrar sedan filformatet till ”.vue” i stället för ”.blade.php” och gör den första bokstaven i namnen till versaler och förvandlar innehållet till en vanlig Vue.js-komponent. Vi kommer att utesluta taggarna <html>, <head> och <body> eftersom de redan ingår i huvudroten för 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>

Det är något som verkligen stör mig! Vi fortsätter att kopiera och klistra in vårt sidhuvud och vår sidfot i varje komponent, vilket inte är någon bra metod. Låt oss skapa en Inertia Basic Layout för att lagra våra beständiga komponenter.

Skapa en mapp som heter ”Layouts” i ”/resources/js”. I den mappen så skapar du en fil som heter ”KinstaLayout.vue”. Den här filen kommer att ha vår rubrik och sidfot och main med en <slot /> för att tillåta att alla komponenter som är förpackade med den här layouten bäddas in i den. Filen ska se ut så här :

”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 ska sedan importera den nya layouten till våra sidor och linda in allt HTML-innehåll i den. Våra komponenter ska se ut så här:

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>

Visa.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 och Inertia Rendering

Låt oss först använda filen ”ArticleFactory” som vi har från startpunkten för vår handledning och sätta in några artiklar i vår databas.

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

Tryck sedan på nedanstående terminalkommando för att migrera dina tabeller och så de falska uppgifterna från fabrikerna:

php artisan migrate:fresh --seed

Detta kommer att skapa 10 falska artiklar i databasen, som vi kommer att behöva skicka till vår vy med hjälp av Laravel-routing. Nu när vi använder Inertia för att rendera vyer så kommer skrivsättet för våra rutter att förändras något. Låt oss skapa vår första Laravel Inertia-rutt i ”routes/web.php” och returnera hemsidevyn från ”/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');

Observera att vi importerade Inertia och inte använde view() Laravel-hjälpen för att returnera vyn. Vi använde istället Inertia::render. Inertia kommer även som standard att leta efter filnamnet som vi nämnde i vår rutt i mappen Pages i ”resources/js”.

Gå till Index-filen och ställ in de hämtade uppgifterna som en prop och loopa över dem med v-for för att visa resultaten. Mellan skripttaggarna så definierar du de överförda uppgifterna som ett rekvisit. Allt Inertia behöver veta är vilken typ av data som du förväntar dig, vilket i vårt fall är ett ”articles”-objekt som inkluderar en array av artiklar.

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

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

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

Observera att det räcker att bara definiera det som en prop utan att returnera det eftersom vi använder formatet setup för Vue.js 3 kompositions-API. Om vi använder options API så skulle vi behöva returnera detta.

Låt oss skapa slingan:

<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 (låt den vara igång eftersom vi använder Vite) och php artisan serve för att starta laravel-utvecklingsservern och komma åt vår webbplats. Nu ser vi den förväntade sidan som visar alla tio artiklarna i databasen.

Vi ska nu använda Google Chrome’s Vue DevTools-tillägg, vilket gör att vi kan felsöka min applikation. Låt oss visa hur våra data skickas till komponenten.

Inspektion av tröghetsegenskaper.
Inspektion av tröghetsegenskaper.

”articles” skickas till komponenten som ett prop-objekt som innehåller en array av artiklar; varje artikel i arrayen är också ett objekt med egenskaper som motsvarar de data som den har fått från databasen. Detta innebär att alla data som vi överför från Laravel till Inertia kommer att behandlas som en prop.

Användning av Tailwind CSS med Inertia.js

Eftersom Tailwind redan är installerat i vårt projekt vid startpunkten är allt vi behöver göra att säga åt det att läsa våra Inertia-komponenter. Detta kan göras genom att redigera ”tailwind.config.js” enligt följande:

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

Kontrollera sedan att vi har importerat vår CSS-fil i ”resources/js/app.js”:

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

Nu är vi redo att styla våra komponenter.

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

Om du tittar i webbläsaren ser du att Vite redan har uppdaterat sidan med Tailwind-magi.

A scrolling image showing a functioning version of the "Kinsta Blog" example from earlier.
Rendering av tröghetsegenskaper.

Tröghetslänkar

Nu när vi har en fungerande hemsida som kan visa alla artiklar i databasen måste vi skapa en annan rutt för att visa enskilda artiklar. Låt oss skapa en ny rutt och ställa in webbadressen till ett ”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 importerade modellen ”Article” och lade till en ny rutt för att returnera Inertia-komponenten Show.vue. Vi utnyttjade även Laravels route model binding, vilket gör att Laravel hämtar den artikel som vi hänvisar till automatiskt.

Allt som vi behöver nu är ett sätt att besöka denna rutt genom att klicka på en länk från hemsidan utan att behöva ladda om hela sidan. Detta är möjligt med Inertia’s magiska verktyg <Link>. Vi nämnde i inledningen att Inertia använder <Link> som en omslagsform för en vanlig ankartagg <a>, och att denna omslagsform är tänkt att göra sidbesöken så smidiga som möjligt. I Inertia så kan <Link> -taggen uppträda som en ankartagg som utför <GET> -förfrågningar, men den kan även fungera som <button> och <form> samtidigt. Låt oss se hur vi kan tillämpa detta i vårt projekt.

I vår Index.vue så kommer vi att importera <Link> från Inertia och ta bort ankartaggarna <a> och ersätta dem med Inertia <Link> -taggar. Attributet href kommer att sättas till den webbadress för rutten som vi tidigare skapade för att visa artikeln:

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

Vi stylar Show.vue med Tailwind för att få den att se lite mer uppklädd ut och redo för vårt besök. Och vi måste även låta den veta att den ska förvänta sig ett ”Article”-objekt och ställa in detta 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 klickar på artikelns titel eller på ”Läs mer” så kommer vi att transporteras till Show.vue utan att uppdatera sidan.

Tröghetslänkar på plats.
Tröghetslänkar på plats.

I vårt fall så använder vi <Link> som en ankartagg som skickar en GET -förfrågan till rutten och returnerar de nya uppgifterna. Vi kan dock även använda <Link> för att POST, PUT, PATCH och DELETE ”routes/web.php”:

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

Laravel Inertia Tips och tricks som du bör känna till

Vi har nu en fungerande SPA som är byggd med Laravel, Inertia och Tailwind CSS. Men inertia kan hjälpa oss att uppnå så mycket mer. Det är dags att skaffa sig några Inertia-tekniker som kommer att vara till hjälp för både utvecklare och applikationsbesökare.

Generering av webbadresser

Du kanske har märkt att vi har lagt till namn till våra Laravel-rutter utan att använda dem. Inertia låter oss använda våra namngivna rutter i våra komponenter istället för att skriva ner hela rutten manuellt.

Vi kan uppnå detta genom att installera Ziggy-paketet i vårt projekt:

composer require tightenco/ziggy

Gå sedan till ”resources/js/app.js” och uppdatera den så här:

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å till ”/resources/views/app.blade.php” och uppdatera sidhuvudet 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>

…och uppdatera dina NPM-paket genom att trycka på nedanstående två terminalkommandon:

npm install && npm run dev

Det här paketet gör det möjligt att använda namngivna rutter i våra Inertia-komponenter, så låt oss gå till Index.vue och ta bort den gamla manuella rutten och ersätta den med ruttnamnet samtidigt som vi skickar data normalt som om vi vore i vår kontrollerare.

Vi kommer att ersätta det här:

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

…med detta:

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

Detta ger oss exakt samma beteende som vi hade, men det är mer utvecklarvänligt och mycket användbart när din rutt förväntar sig många parametrar.

Framstegsindikatorer

Detta är en av de trevligaste funktionerna i Inertia.js. Eftersom SPA ger en interaktiv användarupplevelse så skulle ett fantastiskt tillskott till applikationen vara att ha konstant feedback om huruvida en begäran håller på att laddas. Detta kan åstadkommas genom ett separat bibliotek som Inertia erbjuder.

Biblioteket ”@inertiajs/progress” är en omslagsform kring NProgress som villkorligt visar laddningsindikatorerna enligt Inertia-händelser. Du behöver egentligen inte veta hur detta fungerar bakom kulisserna, så låt oss bara få det att fungera.

Vi kan installera det här biblioteket med nedanstående terminalkommando:

npm install @inertiajs/progress

När det är installerat så måste vi importera 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 });

Detta kommer att visa ett laddningsfält och en laddningsspinner i svart färg, men vi kan ändra färgen samt en massa annat med hjälp av dokumentationen för Inertia.js framstegsindikator.

Inertia’s framstegsindikator (högst upp till höger).
Inertia’s framstegsindikator (högst upp till höger).

Hantering av scrollning

I vissa fall så vill du kanske navigera till en ny sida samtidigt som du behåller samma scrollposition. Du kanske behöver detta om du tillåter användare att lämna kommentarer. Detta kommer att skicka ett formulär och ladda in den nya kommentaren från databasen till din komponent. användaren bör inte förlora scrollpositionen och Inertia tar hand om detta åt oss.

I vårt fall så tillämpar vi detta på vår <Link> -tagg i Index.vue. För att bevara scrollnings-positionen när vi omdirigerar till en annan sida med Inertias <Link> Så behöver vi bara lägga till attributet preserve-scroll till <Link>:

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

SEO-tips

Sedan SPA: s födelse så har människor varit bekymrade över sökmotoroptimering (SEO). Det är allmänt känt att om du använder SPA-metoden så kommer sökmotorerna att ha svårt att genomsöka din webbapplikation eftersom allting renderas på klientsidan. Detta leder till att din webbplats inte visas högst upp i sökresultaten. Men hur kommer det sig att populära plattformar som Facebook och Github nu är SPA: er och fortfarande presterar bra i SEO?

Det här är inte längre ett omöjligt uppdrag. Inertia erbjuder några lösningar för att hjälpa din SPA att bli SEO-vänlig.

Inertia Vue SSR med Laravel och Vite

Sökmotorer letar alltid efter HTML på din webbplats för att kunna identifiera innehållet, men om du inte har HTML i dina webbadresser så blir det här jobbet svårare. När du utvecklar SPA: er så är allt som du har på din sida JavaScript och JSON. Inertia introducerade en SSR-funktion (Server-Side Rendering) som du kan lägga till i din applikation. Detta gör det möjligt för din applikation att förrendera ett första sidbesök på servern och sedan skicka den renderade HTML:en till webbläsaren. Då kan användarna se och interagera med dina sidor innan de laddas helt och hållet, och det finns även andra fördelar. Det förkortar exempelvis den tid som det tar för sökmotorerna att indexera din webbplats.

För att sammanfatta hur det fungerar så identifierar Inertia om den körs på en Node.js-server och renderar komponentnamn, egenskaper, webbadress och version av tillgångar till HTML. Detta kommer att ge användaren och sökmotorn praktiskt taget allt som din sida har att erbjuda.

Eftersom vi har att göra med Laravel så är detta dock föga meningsfullt eftersom Laravel är ett PHP-ramverk och inte körs på en Node.js-server. Vi kommer därför att vidarebefordra vår förfrågan till en Node.js-tjänst, som renderar sidan och returnerar HTML. Detta kommer att göra vår Laravel Vue-applikation SEO-vänlig som standard.

Först så måste vi installera npm-paketet Vue.js SSR:

npm install @vue/server-renderer

Ett annat användbart Inertia ”NPM”-paket tillhandahåller en enkel ”HTTP”-server. Vi rekommenderar starkt att du installerar det:

npm install @inertiajs/server

I ”resources/js/” så lägger vi sedan till en ny fil som heter ssr.js. Den här filen kommer att vara mycket lik den app.js-fil som vi skapade när vi installerade Inertia, men den kommer att exekveras i Node.js i stället för i webbläsaren:

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

Se till att inte inkludera allt i ssr.js-filen eftersom den inte kommer att vara synlig för besökarna; den här filen är endast till för att sökmotorer och webbläsare ska kunna visa data på din sida, så inkludera endast det som är viktigt för dina data eller det som gör dina data tillgängliga.

”Som standard så kommer Inertias SSR-server att fungera på port 13714. Du kan dock ändra detta genom att ange ett andra argument till metoden createServer.” Inertia DOCss.

Inertia.js DOCs förklarar inte hur man integrerar Inertia SSR med Vite, men vi kommer att gå igenom detta nu. Gå till vite.config.js och klistra in nedanstå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,
                },
            },
        }),
    ],
});

Därefter så går du till package.json och ändrar byggskriptet:

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

Om vi nu kör npm run build så kommer Vite att bygga vårt SSR-paket för produktion. För mer information om detta så kan du besöka Inertia SSR DOCs och Vite SSR DOCs.

Titel och Meta

Eftersom JavaScript-applikationerna visas i dokumentets <body> så kan de inte visa upp markeringar i dokumentets <head> eftersom det ligger utanför deras räckvidd. Inertia har dock komponenten <Head> som kan användas för att ställa in sidan <title>, <meta> -taggar och andra <head> -komponenter.

För att lägga till <head> -elementet i din sida så måste vi importera <Head> från Inertia på samma sätt 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 även lägga till en global titel för alla sidor, detta kommer att lägga till ditt applikationsnamn bredvid titeln på varje sida. Vi har redan gjort detta i filen app.js:

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

Det betyder att om vi lägger till <Head title="Home"> på vår applikations hemsida med en titel, så kommer detta att återges så här: <title>Home - My App</title>.

Övervakning av din app

Hastigheten är en av de viktigaste faktorerna för att optimera SEO-prestanda på din webbplats. Om du använder WordPress för din webbplats så kommer Kinsta APM att hjälpa dig att övervaka och hålla ett nära öga på din applikation. Det hjälper dig att identifiera WordPress prestandaproblem och är tillgängligt kostnadsfritt på alla Kinsta-hostade webbplatser.

Sammanfattning

Inertia.js är en av de mest betydelsefulla teknikerna som finns tillgängliga; blanda den med Laravel så får du en modern enkelsidesapplikation som är byggd med PHP och JavaScript. Taylor Otwell, skaparen av Laravel, är så intresserad av Inertia att Laravel har lanserat sina mest populära startpaket, Laravel Breeze och Jetstream, med stöd för Inertia och SSR.

Om du är ett Laravel-fan eller en professionell utvecklare så kommer Inertia.js utan tvekan att fånga din uppmärksamhet. I den här handledningen så skapade vi en mycket enkel och okomplicerad blogg på endast några minuter. Det finns fortfarande mycket att lära sig om inertia, och detta är nog bara den första av många artiklar och handledningar.

Vad vill du att vi ska utforska mer när det gäller Laravel? Låt oss veta detta i kommentarsfältet nedan.

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.