Os aplicativos de várias páginas (MPAs) estão ficando menos populares a cada dia. Plataformas famosas como Facebook, Twitter, YouTube, Github, e muitas outras já estão usando a tecnologia de aplicativos de uma página (SPA).

Essa tecnologia popular permite que os usuários interajam com aplicativos web de maneira rápida e responsiva, porque tudo é renderizado do lado do cliente. No entanto, pode ser um problema para os desenvolvedores que constroem aplicativos renderizadas do lado do servidor com frameworks como Laravel ou Django.

Felizmente, o Inertia.js entrou em cena para simplificar.

Neste artigo, mostraremos como você pode começar a usar o Inertia.js e como usá-lo com Laravel, Vue.js e Tailwind CSS para criar um moderno aplicativo web de blog. Também compartilharemos como tornar os SPAs mais amigáveis em termos de SEO, bem como alguns outros truques.

Se você está apenas começando com Laravel, recomendamos que você leia este artigo primeiro para que você esteja preparado para começar.

Por que SPA?

Antes de perguntarmos por que devemos usar o Inertia, devemos primeiro perguntar: “Por que SPA?”

Por que alguém preferiria aplicativos renderizados do lado do cliente em vez dos tradicionais aplicativos do lado do servidor? O que levaria um desenvolvedor full-stack do Laravel a dizer adeus aos componentes blade?

A resposta básica é: porque velocidade e capacidade de resposta são essenciais para qualquer engajamento bem-sucedido do usuário.

No caso de MPAs, o navegador envia constantemente pedidos ao backend, que então executa inúmeras consultas ao banco de dados. Depois que o banco de dados e o servidor processam as consultas e as entregam ao navegador, a página é renderizada.

Mas os SPAs são diferentes. O aplicativo traz tudo que o usuário precisaria diretamente para a página, eliminando a necessidade do navegador enviar consultas ou recarregar a página para renderizar novos elementos HTML.

Devido a esta experiência única do usuário, grandes empresas estão clamando para que seus sites se tornem aplicativos de uma única página.

Dito isso, criar um aplicativo de página única pode ser difícil para os desenvolvedores de Laravel porque seria necessário que eles começassem a usar Vue.js ou React em vez de modelos de blade, resultando na perda de muitas ferramentas úteis do Laravel que economizam tempo e esforço.

Agora que temos o Inertia.js, tudo isso mudou.

Por que Inertia?

Caso os desenvolvedores do Laravel fossem criar SPAs da web com o Vue antes do Inertia, eles teriam que configurar APIs e retornar dados JSON com o Laravel, depois usar algo como AXIOS para recuperar os dados nos componentes Vue. Eles também precisariam de algo como o Vue Router para gerenciar rotas, o que significaria perder o roteamento do Laravel, bem como middlewares e controladores.

Por outro lado, o Inertia.js permite que os desenvolvedores criem aplicativos modernos de página única com Vue, React e Svelte usando o roteamento e os controladores clássicos do lado do servidor. O Inertia foi projetado para desenvolvedores do Laravel, Ruby on Rails e Django, permitindo que eles construam aplicativos sem mudar suas técnicas de codificação para criar controladores, buscar dados de um banco de dados e renderizar visualizações.

Graças ao Inertia.js, os desenvolvedores de Laravel se sentirão em casa.

Como funciona o Inertia

Construir SPA somente com Laravel e Vue lhe dará uma página JavaScript completa para o seu frontend, mas isso não lhe proporcionará uma experiência de aplicativo de uma única página. Cada link clicado fará com que seu framework do lado do cliente seja reinicializado na próxima página de carregamento.

É aqui que a Inertia entra em cena.

A Inertia é basicamente uma biblioteca de roteamento do lado do cliente. Ela permite que você navegue entre as páginas sem ter que recarregar a página inteira. Isso é feito através do componente <Link>, que é um wrapper leve em torno de uma etiqueta de âncora padrão.

Quando você clica em um link do Inertia, ele intercepta o clique e redireciona para XHR em vez disso. O navegador não recarrega a página desta forma, dando ao usuário uma experiência completa de página única.

Começando com Inertia

Uma página de exemplo feita com o Inertia.js.
Uma página de exemplo feita com o Inertia.js

Para entender o Inertia e como integrá-lo com o Laravel, vamos construir um aplicativo web de blog chamado Kinsta Blog usando a combinação mais poderosa: Laravel para o backend, Vue.js para o frontend em JavaScript e Tailwind CSS para o estilo.

Se você preferir seguir este tutorial em um ambiente local, você pode usar DevKinsta, uma ferramenta poderosa para desenvolvedores, designers e agências que os habilita a construir aplicativos web WordPress de uma e várias páginas. Felizmente, o WordPress pode ser facilmente integrado com o Laravel usando o plano Corcel.

Pré-requisitos

Para obter o máximo deste tutorial, você deve estar familiarizado com o seguinte:

  • Noções básicas de Laravel (instalação, banco de dados, migrações de banco de dados, Templates Eloquentes, controladores e roteamento)
  • Vue.js básico (instalação, estruturas e formulários)

Caso você não esteja se sentindo seguro, confira estes fantásticos tutoriais gratuitos e pagos do Laravel. Caso contrário, vamos começar.

Passo 1: Instalando os elementos principais

Para focar no Inertia.js e ir direto para a parte divertida, certifique-se de ter a seguinte configuração pronta para usar:

  1. Projeto Laravel 9, recém-instalado, chamado kinsta-blog
  2. CSS CLI Tailwind instalado em nosso projeto Laravel
  3. Arquivo de imagem “kinsta-logo.png”. Baixe e descompacte o pacote de logos da Kinsta em https://kinsta.com/press/ e copie o arquivo kinsta-logo2.png para o diretório public/images como kinsta-logo.png.
  4. Dois componentes blade em kinsta-blog/resources/views para visualizar a página inicial do blog e um único artigo no blog, como mostrado abaixo em “/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. Banco de dados local MySQL chamado kinsta_blog conectado ao nosso projeto:”.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 artigo, migrações e 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),
            ];
        }
    }

Isso é tudo o que precisamos para começar! Agora vamos ao que interessa e apresentar o Inertia.js ao nosso projeto.

Passo 2: Instalando o Inertia

O processo de instalação do Inertia é dividido em duas fases principais: do lado do servidor (Laravel) e do lado do cliente (VueJs).

O guia oficial de instalação na documentação da Inertia está um pouco desatualizado porque Laravel 9 agora usa Vite por padrão, mas passaremos por isso também.

1. Server-Side

A primeira coisa que precisamos fazer é instalar adaptadores Inertia server-side com o comando de terminal abaixo via Composer.

composer require inertiajs/inertia-laravel

Agora configuraremos nosso modelo raiz, que será um único arquivo blade que será usado para carregar seus arquivos CSS e JS, assim como uma raiz Inertia que será usada para abrir nosso aplicativo JavaScript.

Como estamos usando a versão mais recente do Laravel 9 v9.3.1, devemos permitir também que a Vite faça sua mágica incluindo-a em /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>

Observe como podemos buscar o título do projeto dinamicamente adicionando o atributo inertia às tags <title>.

Também adicionamos a diretiva @vite ao cabeçalho para informar ao Vite o caminho do nosso arquivo principal JavaScript, onde criamos nosso aplicativo e importamos nosso CSS. O Vite é uma ferramenta que ajuda no desenvolvimento de JavaScript e CSS, permitindo que os desenvolvedores visualizem as alterações de frontend sem precisar atualizar a página durante o desenvolvimento local.

Nosso próximo passo será criar o HandleInertiaRequests middleware e publicá-lo em nosso projeto. Podemos fazer isso disparando o comando do terminal abaixo dentro do diretório raiz do nosso projeto:

php artisan inertia:middleware

Uma vez que isso esteja completo, vá para “App/Http/Kernel.php” e registre HandleInertiaRequests como o último item em seus middlewares da web:

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

2. Client-Side

Em seguida, precisamos instalar as dependências do nosso frontend Vue.js 3 da mesma maneira que no lado do servidor:

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

Em seguida, é necessário importar o Vue.js 3:

npm install vue@next

Então atualize seu arquivo principal JavaScript para inicializar o Inertia.js com Vue.js 3, Vite e Laravel:

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

No snippet de código acima, usamos o plugin do Laravel resolvePageComponent, e dizemos para resolver nossos componentes no diretório ./Pages/$name.vue. Isso porque salvaremos nossos componentes Inertia nesse diretório mais tarde em nosso projeto, e este plugin nos ajudará a carregar automaticamente estes componentes do diretório correto.

Tudo o que resta é instalar vitejs/plugin-vue:

npm i @vitejs/plugin-vue

E atualizar o arquivo 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,
        },
      },
    }),
  ],
});

O passo final é instalar nossas dependências e compilar nossos arquivos:

npm install

npm run dev

E pronto! Você tem um aplicativo Laravel 9 funcional com Vue.js 3 e Vite. Agora é hora de ver o aplicativo em ação!

Criando páginas de Inertia

Você se lembra desses dois arquivos de blade (index e show) para visualizar nossa página inicial e um único artigo?

O único arquivo blade que precisaremos enquanto usamos Inertia é o app.blade.php, que já usamos uma vez quando estávamos instalando Inertia. Então o que acontece com esses arquivos agora?

Transformaremos esses arquivos de componentes de blade em componentes do Inertia.js.

Cada página em seu aplicativo tem seu próprio controlador e componente JavaScript com Inertia. Isso permite que você obtenha apenas os dados necessários para aquela página, sem utilizar uma API. As páginas Inertia nada mais são do que componentes JavaScript, no nosso caso, são componentes Vue.js. Eles não têm nada particularmente digno de nota sobre eles. Então o que faremos é compactar todo o conteúdo HTML entre as tags <template> e qualquer coisa relacionada ao JavaScript será empacotada com as tags <script>.

Crie uma pasta chamada “Páginas” e mova seus arquivos para lá. Então teremos “index.blade.php” e “show.blade.php” em “./resources/js/Pages”. Então alteraremos o formato do arquivo para “.vue” ao invés de “.blade.php” enquanto fazemos a primeira letra de seus nomes em maiúsculas e transformamos seu conteúdo em um componente padrão do Vue.js. Excluiremos as tags <html>, <head>, e <body> porque elas já estão incluídas no componente raiz principal da 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>

Como você pode perceber, continuamos copiando e colando nosso cabeçalho e rodapé em cada componente, o que não é uma prática boa. Criaremos um Layout básico de Inertia para armazenar nossos persistentes componentes.

Crie uma pasta chamada “Layouts” em “/resources/js” e dentro dessa pasta crie um arquivo chamado “KinstaLayout.vue”. Este arquivo terá nosso cabeçalho e rodapé e o main com um <slot /> para permitir que todos os componentes envoltos com este layout sejam incorporados dentro dele. Este arquivo deve ser parecido com esse:

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

Então importaremos este novo layout em nossas páginas e envolver todo o conteúdo HTML com ele. Nossos componentes devem se parecer com isso:

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>

Rotas de Laravel e renderização de Inertia

Primeiro usaremos o arquivo “ArticleFactory” que temos do nosso ponto de partida tutorial e semear alguns artigos em nosso banco de dados.

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

Então clique o comando do terminal abaixo para migrar suas tabelas e semear os dados falsos das fábricas:

php artisan migrate:fresh --seed

Isso criará 10 artigos falsos no banco de dados, que precisaremos passar para a nossa visão usando o roteamento Laravel. Agora que estamos usando Inertia para renderizar views, a maneira como costumávamos escrever nossas rotas irá mudar ligeiramente. Criaremos nossa primeira rota Laravel Inertia em “rotas/web.php” e retornar a visualização da página inicial de “/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');

Note que importamos Inertia e não utilizamos o helper view() Laravel para retornar a visualização, mas em vez disso usamos o Inertia::render. Inertia também procurará por padrão o nome do arquivo que mencionamos em nossa rota na pasta Páginas em “resources/js”.

Vá para o arquivo Index e defina os dados recuperados como uma propriedade e faça um loop sobre eles com v-for para mostrar os resultados. Entre as tags script, defina os dados passados ​​como uma propriedade. Tudo o que o Inertia precisa saber é que tipo de dados você está esperando, que, no nosso caso, é um objeto ‘articles’ contendo um array de artigos.

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

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

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

Note que é suficiente defini-lo apenas como um adereço sem devolvê-lo porque estamos usando o formato setup para a API de composição do Vue.js 3. Se estivermos usando a API de opções, então precisaríamos devolvê-la.

Realizemos o 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 (deixe-o funcionando porque estamos usando Vite) e php artisan serve para iniciar o servidor de desenvolvimento de larvas e acessar nosso site, veremos a página esperada exibindo todos os dez artigos no banco de dados.

Agora, estamos usando a extensão Vue DevTools do Google Chrome, que nos permite depurar o aplicativo. Mostraremos como nossos dados estão sendo passados para o componente.

Inspecionando propriedades de Inertia.
Inspecionando propriedades de Inertia.

“articles” é passado para o componente como um objeto de adereço contendo uma array de artigos; cada artigo da array é também um objeto com propriedades que correspondem aos dados que ele adquiriu do banco de dados. Isso significa que qualquer dado que transferirmos de Laravel para Inertia será tratado como um adereço.

Usando o CSS Tailwind com o Inertia.js

Como o Tailwind já está instalado em nosso projeto desde o início, tudo o que precisamos fazer é dizer a ele para ler nossos componentes Inertia. Isso pode ser feito editando o arquivo “tailwind.config.js” da seguinte forma:

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

Então certifique-se de que importamos nosso arquivo CSS em “resources/js/app.js”:

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

E agora estamos prontos para dar estilo aos nossos 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>

Se você olhar para o navegador, verá que o Vite já atualizou a página com as propriedades mágicas do Tailwind.

Renderização de propriedades de Inertia.
Renderização de propriedades de Inertia.

Links de Inertia

Agora que temos uma página inicial funcional que pode exibir todos os artigos no banco de dados, precisamos criar outra rota para exibir artigos individuais. Criaremos uma nova rota e definiremos a URL para um wildcard de “id”:

“rotas/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 o modelo “Artigo” e adicionamos uma nova rota para retornar o componente Show.vue Inertia. Também aproveitamos a ligação do modelo de rota de Laravel, o que permite que Laravel obtenha automaticamente o artigo ao qual nos referimos.

Tudo o que precisamos agora é de uma maneira de visitar esta rota clicando em um link da página inicial sem ter que recarregar a página inteira. Isso é possível com a ferramenta mágica do Inertia <Link>. Mencionamos na introdução que o Inertia usa <Link> como um wrapper para uma etiqueta de âncora padrão <a>, e que este wrapper tem o objetivo de fazer com que as visitas à página sejam o mais perfeitas possível. No Inertia, a etiqueta <Link> pode se comportar como uma etiqueta de âncora que realiza pedidos <GET>, mas também pode agir como um <button> e um <form> ao mesmo tempo. Veremos como podemos aplicá-la ao nosso projeto.

Em nosso Index.vue, vamos importar <Link> do Inertia, e remover as etiquetas de âncora <a> e substituí-las por etiquetas do Inertia <Link>. O atributo href será definido para a URL da rota que fizemos anteriormente para ver o artigo:

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

Vamos estilizar o arquivo Show.vue com o Tailwind para deixá-lo um pouco mais elegante e pronto para receber nossas visitas. Também precisamos informar que ele deve esperar um objeto “Article” e defini-lo como uma propriedade:

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

Agora quando clicarmos no título do artigo ou “Leia mais”, seremos transportados magicamente para Show.vue sem refrescar a página.

Links de Inertia no lugar.
Links de Inertia no lugar.

Em nosso caso, estamos usando <Link> como uma etiqueta de âncora que envia um pedido GET para a rota e devolve os novos dados, mas podemos usar <Link> para também POST, PUT, PATCH e DELETE

“rotas/web.php”:

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

Laravel Inertia: dicas e truques que você deve conhecer

Agora temos um SPA funcional construído com Laravel, Inertia e Tailwind CSS. Mas a Inertia pode nos ajudar a alcançar muito mais. É hora de adquirir algumas técnicas de Inertia que irão ajudar tanto os desenvolvedores quanto os visitantes de aplicativos.

Gerando URLs

Você deve ter notado que temos adicionado nomes às nossas rotas Laravel sem usá-la. A Inertia nos permite usar nossas rotas nomeadas dentro de nossos componentes ao invés de anotar manualmente a rota completa.

Podemos conseguir isso instalando o pacote Ziggy em nosso projeto:

composer require tightenco/ziggy

A seguir vá para “recursos/js/app.js” e atualize-o assim:

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

Ir para “/resources/views/app.blade.php” e atualizar o cabeçalho com a diretiva @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>

…e atualize seus pacotes de NPM, pressionando os dois comandos de terminal abaixo:

npm install && npm run dev

Este pacote nos permite usar rotas nomeadas dentro de nossos componentes Inertia, então vamos ao Index.vue e removamos a rota manual antiga e a substituamos pelo nome da rota enquanto passamos os dados normalmente como se estivéssemos em nosso controlador.

Substituiremos isso:

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

…com isso:

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

Isso nos dará o mesmo comportamento que tivemos, mas é mais amigável para o desenvolvedor e extremamente útil quando a sua rota espera muitos parâmetros.

Indicadores de progresso

Essa é uma das melhores funcionalidades do Inertia.js; porque o SPA oferece uma experiência interativa ao usuário, ter um feedback constante sobre se uma solicitação está carregando seria uma adição fantástica ao aplicativo. Isso pode ser feito por meio de uma biblioteca separada que o Inertia está oferecendo.

A biblioteca “@inertiajs/progress” é uma camada em torno do NProgress que mostra condicionalmente os indicadores de carregamento de acordo com eventos de Inertia. Você  não precisa realmente saber como isso funciona nos bastidores, então vamos apenas colocá-lo em funcionamento.

Podemos instalar esta biblioteca com o comando de terminal abaixo:

npm install @inertiajs/progress

Uma vez instalado, precisamos importá-lo em “recursos/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 });

Isso mostrará uma barra de carregamento e um spinner de carregamento na cor preta, mas podemos mudar a cor com outras opções úteis que podem ser encontradas na documentação do indicador de progresso Inertia.js.

O indicador de progresso da Inertia (canto superior direito).
O indicador de progresso da Inertia (canto superior direito).

Gerenciamento de rolagem

Em alguns casos, você pode querer navegar para uma nova página enquanto mantém a mesma posição de rolagem. Talvez você precise disso se você permitir que os usuários deixem comentários; isso enviará um formulário e carregará o novo comentário do banco de dados em seu componente; você vai querer que isso aconteça sem que o usuário perca a posição de rolagem. A Inertia cuida disso.

Em nosso caso, aplicaremos isso à nossa tag <Link> no Index.vue. Para preservar a posição de rolagem enquanto redirecionamos para uma página diferente com o <Link> do Inertia, tudo o que precisamos fazer é adicionar o atributo preserve-scroll ao <Link>:

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

Dicas de SEO

Desde o surgimento dos SPAs, as pessoas têm se preocupado com a otimização para mecanismos de pesquisa (SEO). É comum saber que, se você usar a abordagem SPA, os mecanismos de pesquisa terão dificuldade para rastrear seu aplicativo web, pois tudo é renderizado do lado do cliente, resultando em seu site não aparecendo no topo dos resultados de pesquisa; no entanto, como plataformas populares como o Facebook e o Github agora são SPAs e ainda têm um bom desempenho em SEO?

Bem, isso não é mais uma missão impossível. O Inertia oferece algumas soluções para ajudar seu SPA a se tornar amigável para SEO.

Inertia Vue SSR com Laravel e Vite

Os mecanismos de pesquisa estão sempre procurando HTML em seu site para identificar o conteúdo; entretanto, caso você não tenha HTML em suas URLs, este trabalho fica mais difícil. Ao desenvolver SPAs, tudo o que você tem em sua página é JavaScript e JSON. A Inertia introduziu um recurso de renderização do lado do servidor (SSR) que você pode adicionar ao seu aplicativo. Isso permite que seu aplicativo faça uma pré-renderização da página inicial no servidor e então envie o HTML renderizado para o navegador. Isso permite que os usuários vejam e interajam com suas páginas antes que elas sejam totalmente carregadas, e também tem outras vantagens, como encurtar o tempo que os mecanismos de pesquisa levam para indexar seu site.

Para resumir como funciona, Inertia identificará se ele está rodando em um servidor Node.js e renderizará os nomes dos componentes, propriedades, URL e versão dos ativos em HTML. Isso fornecerá ao usuário e ao mecanismo de pesquisa praticamente tudo o que sua página tem a oferecer.

Entretanto, porque estamos lidando com Laravel, isso não faz muito sentido porque Laravel é um framework PHP e não roda em um servidor Node.js. Portanto, encaminharemos o pedido para um serviço Node.js, que irá renderizar a página e retornar HTML. Isso tornará nosso aplicativo Laravel Vue SEO amigável por padrão.

Primeiro, precisamos instalar o pacote Vue.js SSR npm:

npm install @vue/server-renderer

Outro pacote útil do Inertia “NPM” fornece um servidor “HTTP” simples. É fortemente recomendado que você o instale:

npm install @inertiajs/server

Então, em “recursos/js/”, adicionaremos um novo arquivo chamado ssr.js. Este arquivo será muito similar ao arquivo app.js que criamos ao instalar o Inertia, ele será executado somente no Node.js ao invés do 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),
                });
        },
    })
);

Certifique-se de não incluir tudo no arquivo ssr.js, pois ele não será visível aos visitantes; este arquivo é apenas para que os mecanismos de pesquisa e navegadores mostrem os dados dentro da sua página, portanto inclua apenas o que é importante para os seus dados ou apenas o que irá tornar seus dados disponíveis.

“Por padrão, o servidor SSR da Inertia irá operar na porta 13714. Entretanto, você pode mudar isso fornecendo um segundo argumento para o método createServer” DOCss do Inertia.

Os DOCs do Inertia.js não estão explicando como integrar a SSR do Inertia com o Vite, mas explicaremos isso agora. Vá para o vite.config.js e cole o abaixo:

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 seguir, vá para package.json e mude o script de build:

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

Agora se rodarmos npm run build, a Vite irá construir nosso pacote SSR para produção. Para mais informações sobre isso você pode visitar Inertia SSR DOCs e Vite SSR DOCs.

Title e meta

Como os aplicativos JavaScript são renderizados no documento <body>, eles não podem renderizar a marcação para o documento <head> porque ele está fora do seu escopo. Inertia tem um componente <Head> que pode ser usado para definir a página <title>, tags <meta>, e outros componentes <head>.

Para adicionar o elemento <head> à sua Página, devemos importar <Head> do Inertia da mesma forma que fizemos com o componente <Link>:

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

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

Também podemos adicionar um título global para todas as páginas, isso adicionará o nome do seu aplicativo ao lado do título em cada página. Já fizemos isso no arquivo app.js:

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

O que significa que se adicionarmos <Head title="Home"> na página inicial do nosso aplicativo com um título, este será renderizado da seguinte forma: <title>Home - My App</title>.

Monitorando seu aplicativo

A velocidade é um dos fatores mais importantes para otimizar o desempenho de SEO em seu site. Caso você use WordPress para o seu site, Kinsta APM irá ajudá-lo a monitorar e acompanhar de perto o seu aplicativo em ação. Ele ajuda você a identificar problemas de desempenho do WordPress e está disponível gratuitamente em todos os sites hospedados pelo Kinsta.

Resumo

O Inertia.js é uma das tecnologias mais significativas disponíveis; combinando-o com o Laravel, você tem um aplicativo moderno de página única construído com PHP e JavaScript. Taylor Otwell, o criador do Laravel, está tão interessado no Inertia que o Laravel lançou seus kits de início mais populares, Laravel Breeze e Jetstream, com suporte para Inertia e SSR.

Se você é um fã de Laravel ou um desenvolvedor profissional, Inertia.js sem dúvida chamará sua atenção. Neste tutorial, criamos um blog muito básico e direto em apenas alguns minutos. Ainda há muito a aprender sobre Inertia, e este pode ser apenas o primeiro de muitos artigos e tutoriais.

O que mais sobre Laravel você gostaria de explorar? Nos informe na seção de comentários abaixo.

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.