多頁面應用程式(MPA)正變得越來越不流行。著名的平臺,如Facebook、Twitter、YouTube、Github和其他許多平臺已經在使用單頁應用程式(SPA)技術來代替。
這種時髦的技術允許使用者快速地參與到網路應用中去,並且反應迅速,因為一切都在客戶端渲染。然而,對於使用Laravel或Django等框架構建伺服器端渲染的應用程式的開發者來說,這可能是一種痛苦。
幸運的是,Inertia.js插手進來,拯救了我們。
在這篇文章中,我們將展示如何將Inertia.js與Laravel、Vue.js和Tailwind CSS結合在一起使用,以建立一個現代部落格網路應用。我們還將分享如何使SPA更有利於SEO,以及其他一些小技巧。
如果你剛剛開始使用Laravel,我們建議你先閱讀這篇文章,這樣你就可以準備好了。
- 為什麼是SPA?
- 為什麼選擇Inertia?
- Inertia如何工作
- 開始使用Inertia
- 建立Inertia頁面
- Laravel路由和Inertia渲染
- Tailwind CSS與Inertia.js整合
- Inertia連結
- Laravel整合Inertia技巧和竅門
- SEO技巧
為什麼是SPA?
在我們問為什麼要使用Inertia之前,我們必須先問:”為什麼是SPA?”
為什麼會有人喜歡客戶端渲染的應用程式而不是傳統的伺服器端應用程式? 是什麼迫使一個全棧式的Laravel開發者告別了blade元件?
簡短的回答:因為速度和響應性是任何成功的使用者參與的關鍵。
在MPA的情況下, 瀏覽器不斷向後端傳送請求, 然後執行大量的資料庫查詢。在資料庫和伺服器處理查詢並將其傳遞給瀏覽器之後,頁面就被渲染了。
但SPA是不同的。應用程式將使用者需要的一切直接帶到頁面上,消除了瀏覽器傳送查詢或重新載入頁面以呈現新的HTML元素的需要。
由於這種獨一無二的使用者體驗,許多大牌公司都吵著要把他們的網站變成單頁應用程式。
也就是說, 建立一個單頁面應用程式對Laravel開發人員來說是很困難的, 因為這需要他們開始使用Vue.js或React而不是blade模板, 導致許多Laravel寶石的損失, 以節省時間和精力.
不過,現在我們有了Inertia.js,這一切都改變了。
為什麼選擇Inertia?
如果Laravel開發者要在Inertia之前用Vue構建Web SPAs, 他們必須用Laravel設定API並返回JSON資料, 然後用類似AXIOS的東西來檢索Vue元件中的資料. 他們還需要像Vue Router這樣的東西來管理路由,這將意味著失去Laravel的路由,以及中介軟體和控制器。
另一方面, Inertia.js, 使得開發者能夠使用經典的伺服器端路由和控制器來構建現代的單頁Vue, React和Svelte應用程式。Inertia是為Laravel、Ruby on Rails和Django開發者設計的,允許他們在不改變建立控制器、從資料庫獲取資料和渲染檢視的編碼技術的情況下構建應用程式。
多虧了Inertia.js,Laravel開發者會感到賓至如歸。
Inertia如何工作
只用Laravel和Vue構建SPA會給你的前端提供一個完整的JavaScript頁面,但這並不能為你提供一個單頁面的應用體驗。每一個點選的連結都會導致你的客戶端框架在下一個頁面載入時重新啟動。
這就是Inertia進入畫面的地方。
Inertia基本上是一個客戶端路由庫。它允許你在頁面之間進行導航,而不需要重新載入整個頁面。這是通過 <Link>
元件實現的,它是一個圍繞標準錨標籤的輕量級包裝。
當你點選一個Inertia連結時,Inertia會攔截該點選並將你重定向到XHR。瀏覽器不會以這種方式重新載入頁面,給使用者一個完整的單頁體驗。
開始使用Inertia
一個用Inertia.js製作的示例頁面
為了瞭解Inertia以及如何將其與Laravel整合,我們將使用最強大的組合建立一個部落格網路應用,Laravel用於後端,Vue.js用於JavaScript前端,Tailwind CSS用於風格設計。
如果你願意在本地環境下學習這個教程,你可以使用DevKinsta,這是一個為開發者、設計師和機構提供的強大工具,使他們能夠構建單頁和多頁的WordPress網路應用。幸運的是,WordPress可以使用Corcel包輕鬆地與Laravel整合。
前提條件
要從本教程中獲得最大的收穫, 你應該熟悉以下內容:
- Laravel基礎知識(安裝,資料庫,資料庫遷移,Eloquent模型,控制器,和路由)
- Vue.js基礎知識(安裝, 結構, 和表單)
如果你覺得不確定,可以看看這些精彩的Laravel免費和付費教程。否則,讓我們開始吧。
第1步:安裝核心元素
為了專注於Inertia.js並直接進入有趣的部分, 請確保你已經準備好以下設定:
- 新安裝的Laravel 9專案名為
blog
- 在我們的Laravel專案中安裝了Tailwind CSS CLI
- blog/resources/views中的兩個blade元件,用於檢視部落格的主頁和部落格上的一篇文章,如下圖所示:
“/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>部落格 - 閃電博</title></head><body><header><h1>部落格 - 閃電博</h1></header><main><h2>Read our latest articles</h2><section><article><div><img src="/images/wbolt-logo.png" alt="Article thumbnail" /></div><h3>Title for the blog</h3><p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Illum remitaque error vel perferendis aliquam numquam dignissimos, expeditaperspiciatis consectetur!</p><a href="#">Read more</a></article></section></main><footer><h2>Join our Newsletter</h2><input type="email" /></footer></body></html><!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>部落格 - 閃電博</title> </head> <body> <header> <h1>部落格 - 閃電博</h1> </header> <main> <h2>Read our latest articles</h2> <section> <article> <div> <img src="/images/wbolt-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><!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>部落格 - 閃電博</title> </head> <body> <header> <h1>部落格 - 閃電博</h1> </header> <main> <h2>Read our latest articles</h2> <section> <article> <div> <img src="/images/wbolt-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>部落格 - 閃電博</title></head><body><main><article><div><img src="/images/wbolt-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><!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>部落格 - 閃電博</title> </head> <body> <main> <article> <div> <img src="/images/wbolt-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><!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>部落格 - 閃電博</title> </head> <body> <main> <article> <div> <img src="/images/wbolt-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>
- 名為
blog
的MySQL本地資料庫連線到我們的專案:”.env“:DB_CONNECTION=mysqlDB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=blogDB_USERNAME=rootDB_PASSWORD=DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=blog DB_USERNAME=root DB_PASSWORD=DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=blog DB_USERNAME=root DB_PASSWORD=
- 文章模型、遷移和工廠:”app/Models/Article.php“。
<?phpnamespace AppModels;use IlluminateDatabaseEloquentFactoriesHasFactory;use IlluminateDatabaseEloquentModel;class Article extends Model{use HasFactory;protected $fillable = ['title', 'excerpt', 'body'];}<?php namespace AppModels; use IlluminateDatabaseEloquentFactoriesHasFactory; use IlluminateDatabaseEloquentModel; class Article extends Model { use HasFactory; protected $fillable = ['title', 'excerpt', 'body']; }
<?php namespace AppModels; use IlluminateDatabaseEloquentFactoriesHasFactory; use IlluminateDatabaseEloquentModel; class Article extends Model { use HasFactory; protected $fillable = ['title', 'excerpt', 'body']; }
“database/migrations/create_articles_table.php“:
<?phpuse IlluminateDatabaseMigrationsMigration;use IlluminateDatabaseSchemaBlueprint;use IlluminateSupportFacadesSchema;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');}};<?php use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema; 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'); } };<?php use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema; 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“:
<?phpnamespace DatabaseFactories;use IlluminateDatabaseEloquentFactoriesFactory;class ArticleFactory extends Factory{public function definition(){return ['title' => $this->faker->sentence(6),'excerpt' => $this->faker->paragraph(4),'body' => $this->faker->paragraph(15),];}}<?php namespace DatabaseFactories; use IlluminateDatabaseEloquentFactoriesFactory; class ArticleFactory extends Factory { public function definition() { return [ 'title' => $this->faker->sentence(6), 'excerpt' => $this->faker->paragraph(4), 'body' => $this->faker->paragraph(15), ]; } }<?php namespace DatabaseFactories; use IlluminateDatabaseEloquentFactoriesFactory; class ArticleFactory extends Factory { public function definition() { return [ 'title' => $this->faker->sentence(6), 'excerpt' => $this->faker->paragraph(4), 'body' => $this->faker->paragraph(15), ]; } }
這就是我們需要開始工作的全部內容! 現在讓我們進入正題,將Inertia.js引入我們的專案。
第2步:安裝Inertia
Inertia的安裝過程分為兩個主要階段: 伺服器端(Laravel)和客戶端(VueJs).
Inertia文件中的官方安裝指南有點過時了,因為Laravel 9現在預設使用Vite,但我們也會去看看。
1. Server-Side
我們需要做的第一件事是通過Composer用以下終端命令安裝Inertia伺服器端介面卡。
composer require inertiajs/inertia-laravel
現在我們將設定我們的根模板, 這將是一個單一的blade檔案,將用於載入你的CSS和JS檔案,以及一個Inertia根,將用於啟動我們的JavaScript應用程式。
因為我們使用的是最新版本的Laravel 9 v9.3.1, 我們還必須讓Vite發揮它的魔力,在/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" /> <!-- Fetch project name dynamically --> <title inertia>{{ config('app.name', 'Laravel') }}</title> <!-- Scripts --> @vite('resources/js/app.js') @inertiaHead </head> <body class="font-sans antialiased"> @inertia </body> </html>
請注意我們是如何通過在 <title>
標籤中新增 Inertia
屬性來動態地獲取專案標題的。
我們還在頭部新增了 @vite
指令,以便讓Vite知道我們建立應用程式和匯入CSS的JavaScript主檔案的路徑。Vite是一個幫助JavaScript和CSS開發的工具,它允許開發人員在本地開發過程中檢視前端的變化而不必重新整理頁面。
我們的下一步將是建立HandleInertiaRequests中介軟體並將其釋出到我們的專案中。我們可以通過在我們專案的根目錄下發射下面的終端命令來實現。
php artisan inertia:middleware
一旦完成,前往 “App/Http/Kernel “並註冊 HandleInertiaRequests
作為你的網路中介軟體的最後一項:
'web' => [ // ... AppHttpMiddlewareHandleInertiaRequests::class, ],
2. Client-Side
接下來,我們必須以與伺服器端相同的方式安裝我們的前端Vue.js 3依賴項:
npm install @inertiajs/inertia @inertiajs/inertia-vue3 // or yarn add @inertiajs/inertia @inertiajs/inertia-vue3
接下來,你需要拉入Vue.js 3:
npm install vue@next
然後更新你的主要JavaScript檔案,用Vue.js 3、Vite和Laravel初始化Inertia.js:
“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); }, });
在上面的程式碼片段中, 我們使用Laravel的外掛 resolvePageComponent
,並告訴它從目錄./Pages/$name.vue中解析我們的元件。這是因為我們將在以後的專案中把我們的Inertia元件儲存在這個目錄中,而這個外掛將協助我們從正確的目錄中自動載入這些元件。
剩下的就是安裝 vitejs/plugin-vue
:
npm i @vitejs/plugin-vue
並更新 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, }, }, }), ], });
最後一步是安裝我們的依賴項並編譯我們的檔案。
npm install npm run dev
然後就可以了! 你已經有了一個使用Vue.js 3和Vite的Laravel 9應用程式。現在,我們需要看到一些實際發生的情況!
建立Inertia頁面
你還記得那兩個用於檢視我們的主頁和一篇文章的blade檔案(index和show)嗎?
在使用 Inertia 時,我們唯一需要的blade檔案是 app.blade.php,我們在安裝 Inertia 時已經用過一次。那麼,現在這些檔案會怎樣呢?
我們將把這些檔案從blade元件轉化為 Inertia.js 元件。
你的應用程式中的每個頁面都有自己的控制器和Inertia的JavaScript元件。這讓你只獲得該頁面所需的資料,而無需使用API。Inertia頁面只不過是JavaScript元件,在我們的例子中,它們是Vue.js元件。它們並沒有什麼特別值得注意的地方。所以我們要做的是在 <template>
標籤之間包裹所有的HTML內容,任何與JavaScript有關的內容都將用 <script>
標籤來包裹。
建立一個名為 “Pages “的資料夾,將你的檔案移到那裡。因此,我們將把 “index.blade.php“和 “show.blade.php“放在”./resources/js/Pages“。然後我們將改變檔案格式為”.vue”,而不是”.blade.php”,同時使它們名字的第一個字母大寫,並將其內容變成一個標準的Vue.js元件。我們將排除 <html>
, <head>
和 <body>
標籤,因為它們已經包含在主根blade元件中。
“resources/js/Pages/Index.vue“:
<script setup> // </script> <template> <header> <h1>部落格 - 閃電博</h1> </header> <main> <h2>Read our latest articles</h2> <section> <article> <div> <img src="/images/wbolt-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>歡迎來到閃電博的部落格頻道</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>
有一件事真的讓我很困擾!我們一直在每個元件中複製和貼上我們的頁首和頁尾,這不是很好的做法。我們不斷地在每個元件中複製和貼上我們的頁首和頁尾,這並不是一個很好的做法。讓我們建立一個Inertia基本佈局來儲存我們的持久化元件。
在”/resources/js“中建立一個名為 “Layouts “的資料夾,在該資料夾中建立一個名為 “WboltLayout.vue “的檔案。這個檔案將有我們的頁首和頁尾,以及帶有 <slot />
的 main
,以允許所有用這個佈局包裝的元件嵌入其中。這個檔案應該看起來像這樣:
“resources/js/Layouts/WboltLayout.vue“:
<script setup></script> <template> <header> <h1>部落格 - 閃電博</h1> </header> <main> <slot /> </main> <footer> <h2>Join our Newsletter</h2> <input type="email" /> </footer> </template>
然後,我們將在我們的頁面中匯入這個新的佈局,並將所有的HTML內容都包在其中。我們的元件應該看起來像這樣:
Index.vue:
<script setup> import WboltLayout from "../Layouts/WboltLayout.vue"; </script> <template> <WboltLayout> <section> <h2>Read our latest articles</h2> <article> <div> <img src="/images/wbolt-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> </WboltLayout> </template>
Show.vue:
<script setup> import WboltLayout from "../Layouts/WboltLayout.vue"; </script> <template> <WboltLayout> <article> <h1>Title for the blog</h1> <p>Article content goes here</p> </article> </WboltLayout> </template>
Laravel路由和慣性渲染
首先,讓我們使用我們的教程起點中的 “ArticleFactory“檔案,將一些文章播種到我們的資料庫。
“database/seeders/databaseSeeder.php“:
<?php namespace DatabaseSeeders; use AppModelsArticle; use IlluminateDatabaseSeeder; class DatabaseSeeder extends Seeder { public function run() { Article::factory(10)->create(); } }
然後點選下面的終端命令,遷移你的表,並從工廠中播種假資料:
php artisan migrate:fresh --seed
這將在資料庫中建立10個假的文章, 我們將需要使用Laravel路由來傳遞給我們的檢視。現在我們使用Inertia來渲染檢視, 我們過去寫路由的方式將略有改變。讓我們在 “routes/web.php“中建立我們的第一個Laravel Inertia路由,並從”/resources/js/Pages/Index.vue“返回主頁檢視。
“routes/web.php“:
<?php use AppModelsArticle; use IlluminateSupportFacadesRoute; use InertiaInertia; Route::get('/', function () { return Inertia::render('Index', [ 'articles' => Article::latest()->get() ]); })->name('home');
注意,我們匯入了Inertia,沒有使用Laravel的view()助手來返回檢視,而是使用了 Inertia::render
。Inertia也會預設尋找我們在Pages資料夾下 “resources/js “中提到的檔名。
前往索引檔案,並將檢索到的資料設定為道具,用 v-for
在它們上面迴圈,以顯示結果。在指令碼標籤之間,將傳遞的資料定義為一個道具。Inertia需要知道的是你所期望的資料型別,在我們的例子中是一個包含文章陣列的 “Article”物件。
“resources/js/Pages/Index.vue“:
<script setup> import WboltLayout from "../Layouts/WboltLayout.vue"; defineProps({ Articles: Object, }); </script>
注意,只把它定義為一個道具而不返回就足夠了,因為我們使用的是Vue.js 3 composition API的 setup
格式。如果我們使用的是選項API,那麼我們就需要返回它。
讓我們來做這個迴圈:
<template> <WboltLayout> <h2>Read our latest articles</h2> <section> // Looping over articles <article v-for="article in articles":key="article.id"> <div> <img src="/images/wbolt-logo.png" alt="Article thumbnail" /> </div> <h3>{{article.title}}</h3> <p>{{article.excerpt}}</p> <a href="#">Read more</a> </article> </section> </WboltLayout> </template>
npm run dev
(讓它執行,因為我們使用的是Vite)和 php artisan serve
來啟動laravel開發伺服器,訪問我們的網站,我們會看到預期的頁面,顯示資料庫中的所有十篇文章。
現在, 我們正在使用谷歌瀏覽器的Vue DevTools擴充套件, 它允許我們除錯我的應用程式。讓我們來看看我們的資料是如何被傳遞給元件的。
檢查Inertia的屬性
“Articles”作為一個道具物件傳遞給元件, 包含一個文章陣列; 陣列中的每個文章也是一個物件, 其屬性與它從資料庫獲得的資料相對應. 這意味著我們從Laravel傳輸到Inertia的任何資料都將被視為一個道具。
Tailwind CSS與Inertia.js整合
由於Tailwind在開始時已經安裝在我們的專案中,我們所需要做的就是告訴它讀取我們的Inertia元件。這可以通過編輯 “tailwind.config.js“來實現,如下所示:
/** @type {import('tailwindcss').Config} */ module.exports = { content: [ "./storage/framework/views/*.php", "./resources/views/**/*.blade.php", "./resources/js/**/*.vue", ], theme: { extend: {}, }, plugins: [], };
然後確保我們已經在 “resources/js/app.js“中匯入我們的CSS檔案:
import "../css/app.css";
現在我們準備為我們的元件設定樣式。
“resources/js/Pages/Index.vue“:
<script setup> import WboltLayout from "../Layouts/WboltLayout.vue"; defineProps({ articles: Object, }); </script> <template> <WboltLayout> <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/wbolt-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> </WboltLayout> </template>
“resources/js/Layouts/WboltLayout.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">部落格 - 閃電博</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>
如果你看一下瀏覽器,你會發現Vite已經用Tailwind magic更新了頁面。
渲染的Inertia屬性
Inertia連結
現在我們有了一個可以顯示資料庫中所有文章的工作主頁,我們需要建立另一個路由來顯示個別文章。讓我們建立一個新的路由,並將URL設定為 “id “萬用字元:
“routes/web.php”
<?php use AppModelsArticle; use IlluminateSupportFacadesRoute; use InertiaInertia; 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');
我們匯入了“Article”模型,並新增了一條新路線以返回Inertia元件Show.vue。我們還利用了Laravel的路由模型繫結,它允許Laravel自動獲取我們所指的文章。
我們現在需要的是一種方法,通過點選主頁上的連結來訪問這條路線,而無需重新載入整個頁面。這可以通過Inertia的神奇工具 <Link>
實現。我們在介紹中提到,Inertia使用 <Link>
作為標準錨標記 <a>
的包裝器,該包裝器旨在使頁面訪問儘可能無縫。在Inertia中,<Link>
標記可以充當執行 <GET>
請求的錨標記,但它也可以同時充當 <button>
和 <form>
。讓我們看看如何將其應用於我們的專案。
在我們的Index.vue,我們將從Inertia匯入 <Link>
,並移除錨標記 <a>
並將其替換為Inertia的 <Link>
標記。href
屬性將設定為我們之前為檢視文章而建立的路由URL:
<script setup> import WboltLayout from "../Layouts/WboltLayout.vue"; import { Link } from "@inertiajs/inertia-vue3"; defineProps({ articles: Object, }); </script> <template> <WboltLayout> <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/wbolt-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> </WboltLayout> </template>
讓我們使用Tailwind對Show.vue執行樣式化,使其看起來更加整潔,為我們的訪問做好了準備。我們還需要讓它知道,它應該期待一個“Article”物件,並將其設定為道具:
<script setup> import WboltLayout from "../Layouts/WboltLayout.vue"; defineProps({ article: Object, }); </script> <template> <WboltLayout> <article class="mx-auto mt-10 flex justify-center max-w-5xl border-b-2"> <img src="/images/wbolt-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> </WboltLayout> </template>
現在,當我們點選文章標題或“Read more”時,我們將被神奇地傳送到Show.vue,而無需重新整理頁面。
Inertia連結示例
在我們的例子中,我們使用 <Link>
作為錨標記,向路由傳送 GET
請求並返回新資料,但我們也可以使用 <Link>
到 POST
, PUT
, PATCH
和 DELETE
“routes/web.php“:
<Link href="/logout" method="post" as="button" type="button">Logout</Link>
Laravel整合Inertia技巧和竅門
我們現在有一個用Laravel、Inertia和Tailwind CSS構建的工作SPA。但慣性可以幫助我們實現更多。現在是時候掌握一些慣性技術了,這對開發者和應用訪問者都有幫助。
生成URLs
你可能已經注意到,我們一直在為我們的Laravel路由新增名字,卻沒有使用它。Inertia允許我們在元件中使用我們的命名路由,而不是手動寫下完整的路由。
我們可以通過在我們的專案中安裝Ziggy包來實現這個目標:
composer require tightenco/ziggy
然後前往 “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"; 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); }, });
前往”/resources/views/app.blade.php“,用 @route
指令更新頭部:
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Fetch project name dynamically --> <title inertia>{{ config('app.name', 'Laravel') }}</title> <!-- Scripts --> @routes @vite('resources/js/app.js') @inertiaHead </head> <body class="font-sans antialiased"> @inertia </body> </html>
…並通過點選以下兩個終端命令重新整理你的NPM包:
npm install && npm run dev
這個包允許我們在我們的Inertia元件中使用命名的路由,所以讓我們前往Index.vue,並刪除舊的手動路由,用路由名稱替換它,同時像我們在控制器中一樣正常傳遞資料。
我們將替換下面的:
<Link :href="'/posts/' + article.id"> {{ article.title }} </Link>
……為這個:
<Link :href="route('article.show', article.id)"> {{ article.title }} </Link>
這將給我們帶來與我們完全相同的行為,但它對開發者更友好,而且當你的路線期望有許多引數時,它非常有幫助。
進度條指示器
這是Inertia.js最棒的功能之一;因為SPA提供了一種互動式的使用者體驗,如果能不斷地反饋一個請求是否正在載入,這將是對應用程式的一個絕妙補充。這可以通過Inertia提供的一個單獨的庫來實現。
“@inertiajs/progress “庫是一個圍繞NProgress的包裝器,可以根據慣性事件有條件地顯示載入指標。你其實不需要知道它在幕後是如何工作的,所以我們只需要讓它工作。
我們可以用下面的終端命令安裝這個庫:
npm install @inertiajs/progress
一旦它被安裝,我們需要在 “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 });
這將顯示一個黑色的載入條和一個載入旋鈕,但我們可以改變顏色以及其他有用的選項,這些都可以在Inertia.js進度指示器文件中找到。
Inertia進度指示器(右上方)
滾動管理
在某些情況下,你可能想在保持滾動位置不變的情況下導航到一個新的頁面。也許你需要這樣做,如果你允許使用者留下評論;這將提交一個表單,並將新的評論從資料庫載入到你的元件中;你希望這一切發生時使用者不會失去滾動位置。慣性為我們解決了這個問題。
在我們的例子中,讓我們把它應用於Index.vue中的 <Link>
標籤。為了在使用Inertia的 <Link>
重定向到不同的頁面時保留滾動位置,我們所需要做的就是在 <Link>
中新增 preserve-scroll
屬性。
<Link :href="route('article.show', article.id)" preserve-scroll> {{ article.title }} </Link>
SEO技巧
自從SPA誕生以來,人們一直在關注搜尋引擎優化(SEO)問題。人們普遍知道,如果你使用SPA方式,搜尋引擎將很難抓取你的網路應用程式,因為所有的東西都是客戶端渲染的,導致你的網站不能顯示在搜尋結果的頂部;儘管如此,為什麼那些流行的平臺,如Facebook和Github,現在都是SPA,並且在SEO方面仍然表現良好?
好吧,這不再是一個任務:不可能。Inertia正在提供一些解決方案來幫助你的SPA變得對搜尋引擎友好。
Laravel和Vite的Inertia Vue SSR
搜尋引擎總是在你的網站上尋找HTML,以便識別內容;但是,如果你的URL中沒有HTML,這項工作就變得更加困難。在開發SPA時,你的頁面上只有JavaScript和JSON。Inertia引入了一個伺服器端渲染(SSR)功能,你可以將其新增到你的應用程式中。這允許你的應用程式在伺服器上預先渲染一個初始頁面訪問,然後將渲染的HTML傳送到瀏覽器。這可以讓使用者在你的頁面完全載入之前看到並與之互動,而且它還有其他好處,比如縮短搜尋引擎索引你的網站的時間。
總結一下它的工作原理,Inertia將識別它是否在Node.js伺服器上執行,並將元件名稱、屬性、URL和資產版本渲染成HTML。這將為使用者和搜尋引擎提供你的頁面所能提供的幾乎所有內容。
然而, 因為我們正在處理Laravel, 這沒有什麼意義, 因為Laravel是一個PHP框架, 不在Node.js伺服器上執行. 因此, 我們會把請求轉發給一個Node.js服務, 它將渲染頁面並返回HTML. 這將使我們的Laravel Vue應用程式預設為SEO友好。
首先, 我們需要安裝Vue.js SSR npm包:
npm install @vue/server-renderer
另一個有用的Inertia “NPM “包提供了一個簡單的 “HTTP “伺服器。我們強烈建議你安裝它:
npm install @inertiajs/server
然後,在 “resources/js/”中,我們將新增一個名為ssr.js的新檔案。這個檔案將與我們在安裝Inertia時建立的app.js檔案非常相似,只是它將在Node.js而不是瀏覽器中執行:
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), }); }, }) );
確保不要在ssr.js檔案中包含所有內容,因為它不會被訪問者看到;這個檔案只是為了讓搜尋引擎和瀏覽器在你的頁面中顯示資料,所以只包含對你的資料重要的內容,或者只包含能讓你的資料可用的內容。
“預設情況下,Inertia的SSR伺服器將在13714埠執行。然而,你可以通過向createServer方法提供第二個引數來改變這一點。” Inertia DOCss。
Inertia.js DOCs並沒有解釋如何將Inertia SSR與Vite整合在一起,但我們現在會去了解一下。前往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/js/app.js", ssr: "resources/js/ssr.js", }), vue({ template: { transformAssetUrls: { base: null, includeAbsolute: false, }, }, }), ], });
接下來,前往package.json,修改構建指令碼:
"build": "vite build && vite build --ssr"
現在,如果我們執行 npm run build
,Vite將為生產構建我們的SSR包。更多相關資訊,你可以訪問 Inertia SSR 文件 和 Vite SSR 文件。
標題和Meta
因為JavaScript應用程式是在文件的 <body>
中渲染的,它們不能向文件的 <head>
渲染標記,因為它在它們的範圍之外。Inertia有一個 <Head>
元件,可以用來設定頁面的 <title>
, <meta>
標籤和其他 <head>
元件。
要在你的頁面上新增 <head>
元素,我們必須從Inertia匯入 <head>
,就像我們對 <Link>
元件做的那樣:
import { Head } from '@inertiajs/inertia-vue3' <Head> <title>部落格 - 閃電博</title> <meta name="description" content="WordPress愛好者部落格"> </Head>
我們還可以為所有頁面新增一個全域性標題,這將在每個頁面的標題旁邊新增你的應用程式名稱。我們已經在app.js檔案中這樣做了:
createInertiaApp({ title: (title) => `${title} - ${appName}`, // });
這意味著,如果我們在應用程式的主頁中新增 <head title="Homepage">
,並加上一個標題,這將被渲染成這樣: <title>Home - My App</title>
。
監控你的應用程式
速度是優化網站SEO效能的最重要因素之一。如果你的網站使用WordPress,為此,Kinsta APM將協助你監測並密切關注你的應用程式的執行情況,它可以幫助你識別WordPress的效能問題。
小結
Inertia.js是目前最重要的技術之一; 把它和Laravel混合在一起, 你就有了一個用PHP和JavaScript構建的現代單頁面應用程式. Taylor Otwell, Laravel的建立者, 對Inertia非常感興趣,以至於Laravel推出了其最受歡迎的入門套件,Laravel Breeze和Jetstream,並支援Inertia和SSR。
如果你是Laravel的粉絲或者專業的開發者, Inertia.js無疑會吸引你的目光. 在這個教程中,我們只用了幾分鐘就做出了一個非常基本和直接的部落格。關於Inertia,還有很多東西需要學習,這可能只是許多文章和教程中的第一個。
評論留言