Las aplicaciones multipágina (MPA – Multi-Page App en inglés) son cada día menos populares. Plataformas famosas como Facebook, Twitter, YouTube, Github y muchas otras ya utilizan en su lugar la tecnología de las aplicacinerciaiones de una sola página (SPA – single-page application en inglés).

Esta tecnología de moda permite a los usuarios interactuar con las aplicaciones web con rapidez y capacidad de respuesta, porque todo se renderiza en el lado del cliente. Sin embargo, puede ser una molestia para los desarrolladores que crean aplicaciones renderizadas del lado del servidor con frameworks como Laravel o Django.

Afortunadamente, Inertia.js intervino y vino al rescate.

En este artículo, mostraremos cómo puedes empezar a utilizar Inertia.js y cómo utilizarlo con Laravel, Vue.js y Tailwind CSS para crear una moderna aplicación web de blog. También compartiremos cómo hacer que las SPA sean más respetuosas con el SEO, así como algunos otros trucos.

Si acabas de empezar con Laravel, te recomendamos que leas este artículo primero para estar preparado.

¿Por qué SPA?

Antes de preguntarnos por qué deberíamos utilizar Inertia, debemos preguntarnos: «¿Por qué SPA?»

¿Por qué alguien preferiría las aplicaciones renderizadas del lado del cliente a las aplicaciones tradicionales del lado del servidor? ¿Qué obligaría a un desarrollador Laravel full-stack a decir adiós a los componentes blade?

La respuesta corta es: porque la velocidad y la capacidad de respuesta son esenciales para el éxito de cualquier interacción con el usuario.

En el caso de las MPA, el navegador envía constantemente peticiones al backend, que ejecuta numerosas consultas a la base de datos. Después de que la base de datos y el servidor procesen las consultas y las entreguen al navegador, se renderiza la página.

Pero las SPA son diferentes. La aplicación lleva todo lo que el usuario necesitaría directamente a la página, eliminando la necesidad de que el navegador envíe consultas o recargue la página para renderizar nuevos elementos HTML.

Debido a esta experiencia de usuario única, muchas grandes empresas están pidiendo a gritos que sus sitios web se conviertan en aplicaciones de una sola página.

Dicho esto, crear una aplicación de una sola página puede ser difícil para los desarrolladores de Laravel, porque les obligaría a empezar a utilizar Vue.js o React en lugar de plantillas de hoja, lo que supondría la pérdida de muchas gemas de Laravel que ahorran tiempo y esfuerzo.

Sin embargo, ahora que tenemos Inertia.js, eso ha cambiado.

¿Por qué Inertia?

Si los desarrolladores de Laravel construyeran SPA web con Vue antes de Inertia, tendrían que configurar API y devolver datos JSON con Laravel, y luego utilizar algo como AXIOS para recuperar los datos en componentes Vue. También necesitarían algo como Vue Router para gestionar las rutas, lo que significaría perder el enrutamiento de Laravel, así como los middlewares y controladores.

Inertia.js, por otro lado, permite a los desarrolladores construir modernas aplicaciones Vue, React y Svelte de una sola página utilizando enrutamiento y controladores clásicos del lado del servidor. Inertia se diseñó para que los desarrolladores de Laravel, Ruby on Rails y Django pudieran crear aplicaciones sin cambiar sus técnicas de codificación de creación de controladores, obtención de datos de una base de datos y representación de vistas

Gracias a Inertia.js, los desarrolladores de Laravel se sentirán como en casa.

Cómo funciona Inertia

Construir SPA sólo con Laravel y Vue te dará una página JavaScript completa para tu frontend, pero esto no te proporcionará una experiencia de aplicación de una sola página. Cada enlace pulsado hará que tu framework del lado del cliente se reinicie en la siguiente carga de página.

Aquí es donde entra en escena la Inertia.

Inertia es básicamente una biblioteca de enrutamiento del lado del cliente. Te permite navegar entre páginas sin tener que recargar toda la página. Esto se consigue mediante el componente <Link>, que es una envoltura ligera alrededor de una etiqueta de anclaje estándar.

Cuando haces clic en un enlace Inertia intercepta el clic y te redirige a XHR. El navegador no recargará la página de esta forma, proporcionando al usuario una experiencia completa de una sola página.

Primeros pasos con Inertia

Una página de ejemplo hecha con Inertia.js
Una página de ejemplo hecha con Inertia.js

Para entender Inertia y cómo integrarlo con Laravel, vamos a construir una aplicación web de blog llamada Kinsta Blog utilizando el combo más potente, Laravel para el backend, Vue.js para el frontend JavaScript, y Tailwind CSS para el estilo.

Si prefieres seguir este tutorial en un entorno local, puedes utilizar DevKinsta, una potente herramienta para desarrolladores, diseñadores y agencias que les permite construir aplicaciones web de WordPress de una o varias páginas. Afortunadamente, WordPress puede integrarse fácilmente con Laravel utilizando el paquete Corcel.

Requisitos previos

Para sacar el máximo partido a este tutorial, debes estar familiarizado con lo siguiente:

  • Conceptos básicos de Laravel (instalación, base de datos, migraciones de base de datos, Eloquent Models, controladores y enrutamiento)
  • Conceptos básicos de Vue.js (instalación, estructura y formularios)

Si te sientes inseguro, consulta estos fantásticos tutoriales gratuitos y de pago de Laravel. De lo contrario, empecemos.

Paso 1: Instalar los elementos principales

Para centrarte en Inertia.js y pasar directamente a la parte divertida, asegúrate de tener lista la siguiente configuración:

  1. Proyecto Laravel 9 recién instalado con nombre kinsta-blog
  2. Tailwind CSS CLI instalado en nuestro proyecto Laravel
  3. Archivo de imagen «kinsta-logo.png». Descarga y desempaqueta el paquete de logos de Kinsta desde https://kinsta.com/press/, y copia kinsta-logo2.png al directorio public/images como kinsta-logo.png.
  4. Dos componentes blade en kinsta-blog/resources/views para ver la página de inicio del blog y un solo artículo del blog, como se muestra a continuación:»/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. Base de datos local MySQL denominada kinsta_blog conectada a nuestro proyecto:».env«:
    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=kinsta_blog
    DB_USERNAME=root
    DB_PASSWORD=
  6. Modelo de artículo, migraciones y fábricas: «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),
            ];
        }
    }

¡Esto es todo lo que necesitamos para empezar! Ahora pongámonos manos a la obra e introduzcamos Inertia.js a nuestro proyecto.

Paso 2: Instalar Inertia

El proceso de instalación de Inertia se divide en dos fases principales: del lado del servidor (Laravel) y del lado del cliente (VueJs).

La guía oficial de instalación en la documentación de Inertia está un poco desfasada porque Laravel 9 ahora utiliza Vite por defecto, pero también la revisaremos.

1. Del lado del servidor

Lo primero que tenemos que hacer es instalar los adaptadores Inertia del lado del servidor con el siguiente comando de terminal a través de Composer.

composer require inertiajs/inertia-laravel

Ahora configuraremos nuestra plantilla raíz, que será un único archivo blade que se utilizará para cargar tus archivos CSS y JS, así como una raíz Inertia que se utilizará para lanzar nuestra aplicación JavaScript.

Como estamos utilizando la versión más reciente Laravel 9 v9.3.1, también debemos habilitar Vite para que haga su magia incluyéndolo dentro de nuestras etiquetas en /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>

Observa cómo podemos obtener el título del proyecto de forma dinámica añadiendo el atributo inertia a las etiquetas <title>.

También añadimos la directiva @vite a la cabecera para que Vite conozca la ruta de nuestro archivo principal JavaScript donde creamos nuestra aplicación e importamos nuestro CSS. Vite es una herramienta que ayuda en el desarrollo de JavaScript y CSS permitiendo a los desarrolladores ver los cambios en el frontend sin tener que actualizar la página durante el desarrollo local.

Nuestro siguiente paso será crear el middleware HandleInertiaRequests y publicarlo en nuestro proyecto. Podemos hacerlo ejecutando el siguiente comando de terminal dentro del directorio raíz de nuestro proyecto:

php artisan inertia:middleware

Una vez completado esto, dirígete a «App/Http/Kernel.php» y registra HandleInertiaRequests como el último elemento de tus middlewares web:

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

2. Lado del Cliente

A continuación, tenemos que instalar las dependencias de nuestro frontend Vue.js 3 del mismo modo que en el lado del servidor:

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

A continuación, tienes que instalar Vue.js 3:

npm install vue@next

A continuación, actualiza tu archivo JavaScript principal para inicializar Inertia.js con Vue.js 3, Vite y 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);
  },
});

En el fragmento de código anterior, utilizamos el plugin de Laravel resolvePageComponent, y le decimos que resuelva nuestros componentes desde el directorio ./Pages/$name.vue. Esto se debe a que guardaremos nuestros componentes Inertia en este directorio más adelante en nuestro proyecto, y este plugin nos ayudará a cargar automáticamente esos componentes desde el directorio correcto.

Lo único que nos queda es instalar vitejs/plugin-vue:

npm i @vitejs/plugin-vue

Y actualizar el archivo vite.config.js:

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

El último paso es instalar nuestras dependencias y compilar nuestros archivos:

npm install

npm run dev

¡Y voilà! Ya tienes una aplicación Laravel 9 funcionando con Vue.js 3 y Vite. ¡Ahora tenemos que verlo en acción!

Crear páginas de Inertia

¿Recuerdas esos dos archivos blade (index y show) para ver nuestra página de inicio y un solo artículo?

El único archivo blade que necesitaremos mientras utilicemos Inertia es app.blade.php, que ya utilizamos una vez cuando instalamos Inertia. ¿Qué ocurre ahora con esos archivos?

Transformaremos esos archivos de componentes blade en componentes Inertia.js.

Cada página de tu aplicación tiene su propio controlador y componente JavaScript con Inertia. Esto te permite obtener sólo los datos necesarios para esa página, sin utilizar una API. Las páginas Inertia no son más que componentes JavaScript, en nuestro caso, son componentes Vue.js. No tienen nada especialmente destacable. Así que lo que haremos será envolver todo el contenido HTML entre etiquetas <template> y todo lo relacionado con JavaScript se envolverá con etiquetas <script>.

Crea una carpeta llamada «Pages» y mueve tus archivos allí. Así que tendremos «index.blade.php» y «show.blade.php» en «./resources/js/Pages«. A continuación, cambiaremos el formato del archivo a «.vue» en lugar de «.blade.php», haremos que la primera letra de sus nombres esté en mayúsculas y convertiremos su contenido en un componente Vue.js estándar. Excluiremos las etiquetas <html>, <head> y <body> porque ya están incluidas en el componente raíz principal 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>

¡Hay algo que me molesta mucho! Seguimos copiando y pegando nuestro encabezado y pie de página en cada componente, lo que no es una práctica muy buena. Vamos a crear un Layout básico de Inertia para almacenar nuestros componentes persistentes.

Crea una carpeta llamada «Layouts» en «/resources/js» y dentro de esa carpeta crea un archivo llamado «KinstaLayout.vue». Este archivo tendrá nuestra cabecera y pie de página y el main con un <slot /> para permitir que todos los componentes envueltos con este diseño se incrusten dentro de él. Este archivo debería tener el siguiente aspecto :

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

A continuación, vamos a importar este nuevo diseño en nuestras páginas y envolver todo el contenido HTML con él. Nuestros componentes deberían tener este aspecto:

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>

Rutas Laravel e Inertia Render

Primero vamos a utilizar el archivo «ArticleFactory» que tenemos desde el punto de partida de nuestro tutorial y a sembrar algunos artículos en nuestra base de datos.

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

A continuación, utiliza el siguiente comando de terminal para migrar tus tablas y sembrar los datos falsos de las factory:

php artisan migrate:fresh --seed

Esto creará 10 artículos falsos en la base de datos, que tendremos que pasar a nuestra vista utilizando las rutas de Laravel. Ahora que estamos utilizando Inertia para renderizar vistas, la forma en que solíamos escribir nuestras rutas cambiará ligeramente. Creemos nuestra primera ruta Laravel Inertia en «routes/web.php» y devolvamos la vista de la página principal desde «/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');

Observa que hemos importado Inertia y no hemos utilizado el ayudante de Laravel view() para devolver la vista, sino que hemos utilizado Inertia::render. Inertia también buscará por defecto el nombre de archivo que mencionamos en nuestra ruta en la carpeta Pages en «resources/js».

Dirígete al archivo índice y establece los datos recuperados como prop y haz un bucle sobre ellos con v-for para mostrar los resultados. Entre las etiquetas script, define los datos pasados como prop. Todo lo que Inertia necesita saber es qué tipo de datos esperas, que en nuestro caso es un objeto «articles» que contiene una matriz de artículos.

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

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

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

Ten en cuenta que basta con definirlo como prop sin devolverlo porque estamos utilizando el formato setup para la API de composición de Vue.js 3. Si utilizáramos la API de opciones, tendríamos que devolverla.

Hagamos el bucle:

<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 (déjalo en ejecución porque estamos utilizando Vite) y php artisan serve para iniciar el servidor de desarrollo laravel y acceder a nuestro sitio web, veremos la página esperada mostrando los diez artículos de la base de datos.

Ahora, estamos utilizando la extensión Vue DevTools de Google Chrome, que nos permite depurar mi aplicación. Vamos a mostrarte cómo se pasan nuestros datos al componente.

Inspeccionando las propiedades de Inertia.
Inspeccionando las propiedades de Inertia.

«articles» se pasa al componente como un objeto prop que contiene una matriz de artículos; cada artículo de la matriz es también un objeto con propiedades que corresponden a los datos que adquirió de la base de datos. Esto significa que cualquier dato que transfiramos de Laravel a Inertia será tratado como un objeto prop.

Uso de Tailwind CSS con Inertia.js

Dado que ya hemos instalado Tailwind en nuestro proyecto en el punto de partida, todo lo que tenemos que hacer es decirle que lea nuestros componentes de Inertia. Esto se puede conseguir editando «tailwind.config.js» de la siguiente manera:

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

A continuación, asegúrate de que hemos importado nuestro archivo CSS en «resources/js/app.js«:

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

Y ahora estamos listos para dar estilo a nuestros componentes.

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

Si miras el navegador, verás que Vite ya ha actualizado la página con la magia de Tailwind.

Renderizando las propiedades de Inertia.
Renderizando las propiedades de Inertia.

Enlaces de Inertia

Ahora que tenemos una página de inicio que funciona y que puede mostrar todos los artículos de la base de datos, necesitamos crear otra ruta para mostrar artículos individuales. Creemos una nueva ruta y establezcamos la URL con un comodín «id»:

«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');

Importamos el modelo «Artículo» y añadimos una nueva ruta para devolver el componente Show.vue Inertia. También aprovechamos la vinculación del modelo de rutas de Laravel, que permite a Laravel obtener automáticamente el artículo al que nos referimos.

Todo lo que necesitamos ahora es una forma de visitar esta ruta haciendo clic en un enlace de la página principal sin tener que recargar toda la página. Esto es posible con la herramienta mágica de Inertia <Link>. Hemos mencionado en la introducción que Inertia utiliza <Link> como envoltorio de una etiqueta de anclaje estándar <a>, y que este envoltorio está pensado para que las visitas a la página sean lo más fluidas posible. En Inertia, la etiqueta <Link> puede comportarse como una etiqueta de anclaje que realiza peticiones <GET>, pero también puede actuar como una <button> y una <form> al mismo tiempo. Veamos cómo podemos aplicarlo a nuestro proyecto.

En nuestro Index.vue, importaremos <Link> de Inertia, y eliminaremos las etiquetas de anclaje <a> y las sustituiremos por etiquetas <Link> de Inertia. El atributo href se establecerá en la URL de la ruta que hicimos anteriormente para ver el artículo:

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

Estilicemos Show.vue con Tailwind para que parezca un poco más arreglado y listo para nuestra visita. Y también tenemos que hacerle saber que debe esperar un objeto «Artículo» y establecerlo como 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>

Ahora, cuando hagamos clic en el título del artículo o en «Read more», seremos transportados mágicamente a Show.vue sin actualizar la página.

Enlaces de Inertia en su sitio.
Enlaces de Inertia en su sitio.

En nuestro caso, estamos utilizando <Link> como etiqueta de anclaje que envía una petición GET a la ruta y devuelve los nuevos datos, pero podemos utilizar <Link> para también POST, PUT, PATCH y DELETE

«routes/web.php«:

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

Trucos y consejos de Laravel Inertia que debes conocer

Ya tenemos una SPA funcional construida con Laravel, Inertia y Tailwind CSS. Pero Inertia puede ayudarnos a conseguir mucho más. Es hora de adquirir algunas técnicas de Inertia que ayudarán tanto a los desarrolladores como a los visitantes de la aplicación.

Generar URLs

Te habrás dado cuenta de que hemos estado añadiendo nombres a nuestras rutas Laravel sin utilizarlas. Inertia nos permite utilizar nuestras rutas con nombre dentro de nuestros componentes en lugar de escribir manualmente la ruta completa.

Podemos conseguirlo instalando el paquete Ziggy en nuestro proyecto:

composer require tightenco/ziggy

A continuación, dirígete a «resources/js/app.js» y actualízalo de la siguiente manera:

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

Dirígete a «/resources/views/app.blade.php» y actualiza la cabecera con la directiva @routes:

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

…y actualiza tus paquetes NPM pulsando los dos comandos de terminal siguientes:

npm install && npm run dev

Este paquete nos permite utilizar rutas con nombre dentro de nuestros componentes Inertia, así que dirijámonos a Index.vue y eliminaremos la antigua ruta manual y la sustituiremos por el nombre de la ruta mientras pasamos los datos normalmente como si estuviéramos en nuestro controlador.

Reemplazaremos esto

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

…por esto:

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

Esto nos dará exactamente el mismo comportamiento que teníamos, pero es más amigable para el desarrollador y extremadamente útil cuando tu ruta espera muchos parámetros.

Indicadores de progreso

Esta es una de las características más agradables de Inertia.js; dado que SPA proporciona una experiencia de usuario interactiva, disponer de información constante sobre si una solicitud se está cargando sería una fantástica adición a la aplicación. Esto puede conseguirse mediante una biblioteca independiente que ofrece Inertia.

La biblioteca «@inertiajs/progress» es una envoltura alrededor de NProgress que muestra condicionalmente los indicadores de carga según los eventos de Inertia. Realmente no necesitas saber cómo funciona esto entre bastidores, así que vamos a ponerlo en marcha.

Podemos instalar esta biblioteca con el siguiente comando de terminal:

npm install @inertiajs/progress

Una vez instalada, tenemos que importarla en «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 });

Esto mostrará una barra de carga y un spinner de carga en color negro, pero podemos cambiar el color junto con otras opciones útiles que se pueden encontrar en la documentación del indicador de progreso Inertia.js.

El indicador de progreso de Inertia (arriba a la derecha).
El indicador de progreso de Inertia (arriba a la derecha).

Gestión del desplazamiento

En algunos casos, puede que quieras navegar a una nueva página manteniendo la misma posición de desplazamiento. Quizá lo necesites si permites a los usuarios dejar comentarios; esto enviará un formulario y cargará el nuevo comentario desde la base de datos a tu componente; querrás que esto ocurra sin que el usuario pierda la posición de desplazamiento. Inertia se encarga de esto por nosotros.

En nuestro caso, vamos a aplicarlo a nuestra etiqueta <Link> en Index.vue. Para conservar la posición de desplazamiento al redirigir a una página diferente con <Link> de Inertia, lo único que tenemos que hacer es añadir el atributo preserve-scroll a la etiqueta <Link>:

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

Consejos SEO

Desde el nacimiento de las SPA, la gente se ha preocupado por la optimización para motores de búsqueda (SEO). Se sabe que si utilizas el enfoque SPA, los motores de búsqueda tendrán dificultades para rastrear tu aplicación web porque todo se renderiza en el lado del cliente, lo que hace que tu sitio web no aparezca en los primeros resultados de búsqueda; sin embargo, ¿cómo es que plataformas tan populares como Facebook y Github son ahora SPA y siguen funcionando bien en SEO?

Bueno, esto ya no es una misión imposible. Inertia ofrece algunas soluciones para ayudar a que tu SPA sea amigable con el SEO.

Inertia Vue SSR con Laravel y Vite

Los motores de búsqueda siempre buscan HTML en tu sitio web para identificar el contenido; sin embargo, si no tienes HTML en tus URL, este trabajo se hace más difícil. Cuando desarrollas SPAs, todo lo que tienes en tu página es JavaScript y JSON. Inertia introdujo una función de renderizado del lado del servidor (SSR) que puedes añadir a tu aplicación. Esto permite a tu aplicación pre-renderizar una visita inicial a la página en el servidor y luego enviar el HTML renderizado al navegador. Esto permite a los usuarios ver e interactuar con tus páginas antes de que se carguen completamente, y también tiene otras ventajas, como acortar el tiempo que tardan los motores de búsqueda en indexar tu sitio.

Para resumir cómo funciona, Inertia identificará si se está ejecutando en un servidor Node.js y renderizará en HTML los nombres de los componentes, las propiedades, la URL y la versión de los activos. Esto proporcionará al usuario y al motor de búsqueda prácticamente todo lo que tu página tiene que ofrecer.

Sin embargo, como estamos tratando con Laravel, esto no tiene mucho sentido porque Laravel es un framework PHP y no se ejecuta en un servidor Node.js. Por lo tanto, reenviaremos la petición a un servicio Node.js, que renderizará la página y devolverá HTML. Esto hará que nuestra aplicación Laravel Vue sea SEO friendly por defecto.

En primer lugar, tenemos que instalar el paquete npm Vue.js SSR:

npm install @vue/server-renderer

Otro útil paquete «NPM» de Inertia proporciona un sencillo servidor «HTTP». Se recomienda encarecidamente que lo instales:

npm install @inertiajs/server

A continuación, en «resources/js/», añadiremos un nuevo archivo llamado ssr.js. Este archivo será muy similar al archivo app.js que creamos al instalar Inertia, sólo que se ejecutará en Node.js en lugar de en el navegador:

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

Asegúrate de no incluir todo en el archivo ssr.js, ya que no será visible para los visitantes; este archivo es sólo para que los motores de búsqueda y los navegadores muestren los datos dentro de tu página, así que incluye sólo lo que sea importante para tus datos o sólo lo que haga que tus datos estén disponibles.

«Por defecto, el servidor SSR de Inertia funcionará en el puerto 13714. Sin embargo, puedes cambiar esto proporcionando un segundo argumento al método createServer». Inertia DOCss.

Los DOCs de Inertia.js no explican cómo integrar el SSR de Inertia con Vite, pero lo haremos ahora. Dirígete a vite.config.js y pega lo siguiente:

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

A continuación, dirígete a package.json y cambia el script de compilación:

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

Ahora, si ejecutamos npm run build, Vite construirá nuestro paquete SSR para producción. Para más información sobre esto puedes visitar Inertia SSR DOCs y Vite SSR DOCs.

Título y Meta

Dado que las aplicaciones JavaScript se renderizan dentro del <body> del documento, no pueden renderizar marcas en el <head> del documento porque están fuera de su alcance. Inertia tiene un componente <Head> que puede utilizarse para establecer la página <title>, las etiquetas <meta> y otros componentes <head>.

Para añadir el elemento <head> a tu página, debemos importar <Head> de Inertia igual que hicimos con el componente <Link>:

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

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

También podemos añadir un título global para todas las páginas, esto añadirá el nombre de tu aplicación junto al título en todas las páginas. Ya lo hemos hecho en el archivo app.js:

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

Lo que significa que si añadimos <Head title="Home"> en la página principal de nuestra aplicación con un título, éste se mostrará así: <title>Home - My App</title>.

Monitorizar tu aplicación

La velocidad es uno de los factores más importantes para optimizar el rendimiento SEO de tu sitio web. Si utilizas WordPress para tu sitio web, por esta razón, Kinsta APM te ayudará a monitorizar y vigilar de cerca tu aplicación en acción. Te ayuda a identificar los problemas de rendimiento de WordPress y está disponible de forma gratuita para todos los sitios alojados en Kinsta.

Resumen

Inertia.js es una de las tecnologías más significativas disponibles; mézclala con Laravel y tendrás una moderna aplicación de una sola página construida con PHP y JavaScript. Taylor Otwell, el creador de Laravel, está tan interesado en Inertia que Laravel ha lanzado sus kits de inicio más populares, Laravel Breeze y Jetstream, con soporte para Inertia y SSR.

Si eres un fan de Laravel o un desarrollador profesional, Inertia.js sin duda te llamará la atención. En este tutorial, hemos creado un blog muy básico y sencillo en sólo unos minutos. Todavía hay mucho que aprender sobre Inertia, y éste puede ser sólo el primero de muchos artículos y tutoriales.

¿Qué más te gustaría que exploráramos sobre Laravel? Háznoslo saber en la sección de comentarios más abajo.

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.