> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Svelte 入門 — 搭配 Inertia × Laravel 使用的基礎知識

> Laravel + Inertia.js 中使用 Svelte 的入門指南。從編譯器導向的設計思路、Svelte 5 的 Runes 語法，到 useForm、usePage 等 Inertia Svelte hooks，實用地一次講解。

## 什麼是 Svelte

[Svelte](https://svelte.jp/) 在 JavaScript 框架中佔有相當獨特的位置。相對於 React 或 Vue 是以**執行時函式庫**運作，Svelte 是以**編譯器**運作。它在建置時將元件轉換為純 JavaScript，因此不需要將額外的框架程式碼傳送到瀏覽器。

Svelte 最大的特色是**不使用 Virtual DOM**。當狀態變化時，Svelte 在編譯時產生的程式碼會直接更新 DOM。這使得非常輕量且高效能的 UI 得以實現。

<Info>
  本頁介紹的是 Svelte 5 與 Inertia v3 的組合。Laravel 13 的入門套件預設使用此組合。
</Info>

### Svelte 5 的 Runes

Svelte 5（2024 年釋出）引入了名為 **Runes（符文）** 的新反應式系統。透過 `$state`、`$derived`、`$effect` 等特殊函式（runes）來宣告反應式狀態。與 Svelte 4 以前的隱式反應式不同，此設計是明確且更容易預測的。

```svelte theme={null}
<script lang="ts">
    let count = $state(0)

    function increment() {
        count++
    }
</script>

<button onclick={increment}>{count}</button>
```

<Tip>
  Laravel 的 Svelte 入門套件以 Svelte 5 + TypeScript 為標準。本頁的所有範例皆以 TypeScript（`lang="ts"`）撰寫。
</Tip>

***

## 在 Laravel 中的定位

### 歷史

Svelte 與 Laravel 的關係比 Vue、React 都要新，官方支援是從**官方入門套件**的採用開始。

```mermaid theme={null}
timeline
    title Laravel 與 Svelte 的軌跡
    2016 : Svelte 1 釋出（作者 Rich Harris）
    2019 : Svelte 3 — 重新設計為編譯器導向
    2021 : Inertia.js — 由社群開始提供 Svelte adapter
    2024 : Svelte 5 — 引入 Runes 語法
    2026 : Laravel 13 — 官方將 Svelte 入門套件正式加入（支援 Inertia v3）
```

**2026 年的 Laravel 13** 將 Svelte 正式加入入門套件後，Svelte 也成為 Laravel 生態系中官方前端選項之一。它與 Vue、React 並列，可在 `laravel new` 的互動式提示中選擇。

雖然對 Laravel 使用者來說仍是相對陌生的框架，但其特徵在於**由編譯器產生的輕量 bundle**與**簡潔的語法**。從 Vue 或 React 移轉過來，可能會對其簡潔的寫法感到驚訝。

### 目前的主流方式：Inertia × Svelte

目前在 Laravel 中使用 Svelte 的主要方式是 **Inertia × Svelte**。Inertia 不需要設計 API，即可從 Laravel 的 controller 直接將資料傳遞給 Svelte 元件，實現「現代單體式（modern monolith）」架構。

```mermaid theme={null}
graph LR
    Browser["瀏覽器"]
    Inertia["Inertia.js<br>（Adapter 層）"]
    Laravel["Laravel<br>（Controller）"]
    Svelte["Svelte<br>（頁面元件）"]

    Browser <-->|XHR / 完整頁面載入| Inertia
    Inertia <-->|Inertia 回應| Laravel
    Inertia -->|props| Svelte
    Svelte -->|渲染| Browser
```

***

## 安裝設定

### 透過入門套件（推薦）

要新建專案時，使用入門套件是最方便的方式。

```shell theme={null}
laravel new my-app
```

在互動式提示中選擇 **Svelte**，以下項目就會全部自動設定完成。

* `inertiajs/inertia-laravel`（伺服器端 adapter）
* `@inertiajs/svelte`（客戶端 adapter）
* `svelte` + `@sveltejs/vite-plugin-svelte`（Svelte 5 主體與 Vite 外掛）
* TypeScript + `svelte-check`
* Tailwind CSS + shadcn-svelte 元件庫
* `HandleInertiaRequests` middleware
* 登入、註冊等認證畫面（以 Inertia + Svelte + TypeScript 實作完畢）

### 手動安裝

要加入既有專案時，需將伺服器端與客戶端分開安裝。

```shell theme={null}
# 伺服器端（PHP）
composer require inertiajs/inertia-laravel

# 客戶端（JavaScript）
npm install @inertiajs/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
```

接著，在 `vite.config.ts` 中加入 Svelte plugin 與 Inertia Vite plugin。

```ts theme={null}
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import inertia from '@inertiajs/vite'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.ts'],
            refresh: true,
        }),
        svelte(),
        inertia(),
    ],
})
```

在 `resources/js/app.ts` 啟動 Inertia 應用。由於 `@inertiajs/vite` 外掛會自動解析頁面並掛載，只需極簡的進入點即可。

```ts theme={null}
import { createInertiaApp } from '@inertiajs/svelte'

createInertiaApp()
```

<Info>
  手動安裝的詳情（root template 的設定、middleware 註冊等）請參考 [Inertia 官方文件](https://inertiajs.com/installation)。
</Info>

***

## 目錄結構

入門套件將 Svelte 的頁面元件放在 `resources/js/pages/` 目錄下。

```
resources/js/
├── app.ts             # Inertia 應用的起點
├── components/        # 可重用的 UI 元件
│   └── ui/            # shadcn-svelte 元件
├── layouts/           # 版面配置元件
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # 工具函式、Svelte Runes 模組
├── pages/             # Inertia 頁面元件（對應 controller 名稱）
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # TypeScript 型別定義
```

寫成 `Inertia::render('posts/Index', [...])` 時，`resources/js/pages/posts/Index.svelte` 就會是對應的元件。

***

## Svelte 檔案的基本結構

`.svelte` 檔案由 **`<script>`、模板、`<style>`** 三個區塊組成。

```svelte theme={null}
<script lang="ts">
    // 邏輯（TypeScript）
    let name = $state('Laravel')
</script>

<!-- 模板（類似 HTML 的寫法） -->
<h1>Hello, {name}!</h1>

<style>
    /* 帶 scope 的 CSS（只套用在此元件） */
    h1 {
        color: #ff2d20;
    }
</style>
```

結構類似 Vue 的 Single File Component（SFC），但模板與腳本的程式碼量更少是其特色。`<style>` 預設就是 scope 限定的，因此不必擔心類別名衝突。

### 模板語法

#### 變數展開與運算式

在模板中使用 `{}` 內嵌 JavaScript 值或運算式。

```svelte theme={null}
<script lang="ts">
    let name = $state('世界')
    let count = $state(3)
</script>

<p>你好，{name}!</p>
<p>兩倍是 {count * 2}</p>
```

#### `{#if}` — 條件分支

```svelte theme={null}
<script lang="ts">
    let isLoggedIn = $state(false)
    let role = $state('editor')
</script>

{#if isLoggedIn}
    <p>歡迎！</p>
{:else if role === 'admin'}
    <p>以管理員身份登入中</p>
{:else}
    <a href="/login">登入</a>
{/if}
```

相當於 Vue 的 `v-if` / `v-else`、React 的三元運算子。

#### `{#each}` — 清單渲染

```svelte theme={null}
<script lang="ts">
    type Post = { id: number; title: string }
    let posts = $state<Post[]>([
        { id: 1, title: '第一篇貼文' },
        { id: 2, title: '第二篇貼文' },
    ])
</script>

<ul>
    {#each posts as post (post.id)}
        <li>{post.title}</li>
    {/each}
</ul>
```

`(post.id)` 為 key 指定，用於高效的差異更新。相當於 Vue 的 `v-for` / React 的 `Array.map()`。

#### `bind:` — 雙向繫結

使用 `bind:value` 可讓表單元素的值與反應式變數進行雙向同步。

```svelte theme={null}
<script lang="ts">
    let title = $state('')
    let agreed = $state(false)
    let role = $state('viewer')
</script>

<!-- 文字輸入 -->
<input bind:value={title} type="text" />
<p>輸入中: {title}</p>

<!-- 核取方塊 -->
<input bind:checked={agreed} type="checkbox" />
<p>同意: {agreed}</p>

<!-- 下拉選單 -->
<select bind:value={role}>
    <option value="viewer">閱覽者</option>
    <option value="editor">編輯者</option>
    <option value="admin">管理員</option>
</select>
```

相當於 Vue 的 `v-model`。React 需要手寫 `onChange` handler，但 Svelte 只需 `bind:` 就能以宣告式撰寫。

***

## 頁面元件的基本

Inertia 的頁面元件就是一般的 Svelte 元件。可將從 Laravel controller 傳入的資料作為 props 接收。

### Controller

```php theme={null}
// app/Http/Controllers/PostController.php
use Inertia\Inertia;
use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('posts/Index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }
}
```

### Svelte 頁面元件

Svelte 5 中使用 `$props()` rune 接收 props。

```svelte theme={null}
<!-- resources/js/pages/posts/Index.svelte -->
<script lang="ts">
    import { Link } from '@inertiajs/svelte'

    type Post = {
        id: number
        title: string
        created_at: string
    }

    type Props = {
        posts: {
            data: Post[]
        }
    }

    let { posts }: Props = $props()
</script>

<div>
    <h1>貼文列表</h1>
    {#each posts.data as post (post.id)}
        <article>
            <h2>
                <Link href={`/posts/${post.id}`}>{post.title}</Link>
            </h2>
            <p>{post.created_at}</p>
        </article>
    {/each}
</div>
```

只需以 `$props()` 接收 props，即可在模板中使用 controller 傳入的資料。不需要定義 REST API。

***

## `Link` 元件

使用 `@inertiajs/svelte` 提供的 `<Link>` 元件時，頁面切換以 XHR 進行，可避免瀏覽器整頁重新載入。

```svelte theme={null}
<script lang="ts">
    import { Link } from '@inertiajs/svelte'
</script>

<!-- 基本連結 -->
<Link href="/posts">貼文列表</Link>

<!-- 使用 POST 方法的連結（如刪除） -->
<Link href="/posts/1" method="delete" as="button">
    刪除
</Link>

<!-- 預先載入（滑鼠 hover 時預取資料） -->
<Link href="/posts/1" preload>查看貼文</Link>
```

寫法與一般 `<a>` 標籤相同，但背後 Inertia 只會替換頁面元件，帶來如 SPA 般的操作體驗。

***

## `Form` 元件

`@inertiajs/svelte` 提供的 `<Form>` 元件是入門套件認證畫面中所採用的表單送出推薦寫法。以 props 指定 `action` 與 `method`，內容以 **`{#snippet}`** 撰寫。

### 基本用法

```svelte theme={null}
<script lang="ts">
    import { Form } from '@inertiajs/svelte'
</script>

<Form action="/posts" method="post" class="flex flex-col gap-4">
    {#snippet children({ errors, processing })}
        <div>
            <label for="title">標題</label>
            <input id="title" name="title" type="text" required />
            {#if errors.title}
                <p class="error">{errors.title}</p>
            {/if}
        </div>

        <div>
            <label for="content">內文</label>
            <textarea id="content" name="content"></textarea>
            {#if errors.content}
                <p class="error">{errors.content}</p>
            {/if}
        </div>

        <button type="submit" disabled={processing}>
            {processing ? '傳送中...' : '發布'}
        </button>
    {/snippet}
</Form>
```

`{#snippet children({ errors, processing })}` 是 Svelte 的 snippet 語法，用於傳遞給元件的內容區塊（相當於 Vue 的 slot 或 React 的 render props）。`errors` 與 `processing` 由 `Form` 元件自動計算並傳入。表單欄位不使用 `bind:value`，而是使用 HTML 原生的 `name` 屬性，讓瀏覽器標準的表單資料蒐集機制正常運作。

### 入門套件的模式

入門套件使用 [Wayfinder](/zh-TW/blog/wayfinder-introduction) 以物件形式管理路由。`store.form()` 會回傳包含路由物件 `action` 與 `method` 的物件，可用 spread 傳入 `<Form>`。

```svelte theme={null}
<script lang="ts">
    import { Form } from '@inertiajs/svelte'
    import { store } from '@/routes/login'
</script>

<Form
    {...store.form()}
    resetOnSuccess={['password']}
    class="flex flex-col gap-6"
>
    {#snippet children({ errors, processing })}
        <!-- 表單內容 -->
    {/snippet}
</Form>
```

指定為 `resetOnSuccess` 的欄位會在送出成功時自動重設。適用於密碼欄位等送出後想清空的欄位。

<Info>
  若不使用 Wayfinder，也可直接傳入 `action="/login"` 之類的 URL，效果相同。
</Info>

***

## `useForm` hook

表單處理使用 `@inertiajs/svelte` 的 `useForm` hook。可以簡潔地實作表單狀態管理、送出與驗證錯誤顯示。

### Controller 側

```php theme={null}
// app/Http/Controllers/PostController.php
class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', '貼文已建立。');
    }
}
```

### Svelte 表單元件

```svelte theme={null}
<!-- resources/js/pages/posts/Create.svelte -->
<script lang="ts">
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({
        title: '',
        content: '',
    })

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/posts')
    }
</script>

<form onsubmit={submit}>
    <div>
        <label>標題</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>內文</label>
        <textarea bind:value={form.data.content}></textarea>
        {#if form.errors.content}
            <p class="error">{form.errors.content}</p>
        {/if}
    </div>

    <button type="submit" disabled={form.processing}>
        {form.processing ? '傳送中...' : '發布'}
    </button>
</form>
```

以下整理 `useForm` 回傳物件的主要屬性。

| 屬性 / 方法            | 說明                  |
| ------------------ | ------------------- |
| `form.data`        | 表單資料物件              |
| `form.errors`      | 驗證錯誤（以欄位名存取）        |
| `form.processing`  | 傳送中為 `true`（用於禁用按鈕） |
| `form.isDirty`     | 若已從初始值變更則為 `true`   |
| `form.post(url)`   | 以 POST 請求送出         |
| `form.put(url)`    | 以 PUT 請求送出（更新）      |
| `form.delete(url)` | 以 DELETE 請求送出       |
| `form.reset()`     | 重設表單為初始值            |

當驗證錯誤返回時，`useForm` 會保留輸入內容並顯示錯誤。搭配 `bind:value` 即可實現無縫的表單體驗。

***

## 共享資料（Shared Data）

所有頁面都需要的共通資料（登入使用者資訊、flash 訊息等），在 `HandleInertiaRequests` middleware 的 `share()` 方法中定義。

```php theme={null}
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],
            'flash' => [
                'success' => $request->session()->get('success'),
                'error'   => $request->session()->get('error'),
            ],
        ]);
    }
}
```

在 Svelte 元件中透過 `usePage()` 存取共享資料。

```svelte theme={null}
<script lang="ts">
    import { usePage } from '@inertiajs/svelte'

    type SharedProps = {
        auth: {
            user: { id: number; name: string; email: string } | null
        }
        flash: {
            success: string | null
            error: string | null
        }
    }

    const page = usePage<SharedProps>()
</script>

<header>
    {#if page.props.auth.user}
        <span>{page.props.auth.user.name}</span>
    {:else}
        <span>訪客</span>
    {/if}
</header>

{#if page.props.flash.success}
    <div class="alert-success">{page.props.flash.success}</div>
{/if}
```

<Info>
  共享資料會包含在所有請求中，因此建議只放最必要的資料。使用 `fn()` 進行 lazy 求值時，只在實際被存取時才會被求值。
</Info>

***

## Svelte 5 的反應式（Runes）

以下介紹以 Inertia × Svelte 開發時，需要了解的 Svelte 5 Runes 語法。

### `$state` — 反應式狀態

```svelte theme={null}
<script lang="ts">
    let count = $state(0)
    let isOpen = $state(false)
    let items = $state<string[]>([])
</script>

<p>{count}</p>
<button onclick={() => count++}>+1</button>
<button onclick={() => isOpen = !isOpen}>切換</button>
```

以 `$state` 宣告的變數會自動變為反應式。值變化時 DOM 會自動更新。相當於 Vue 的 `ref` 或 React 的 `useState`，但不需要存取 `.value` 屬性，直接指派即可更新狀態。

### `$derived` — 衍生值

```svelte theme={null}
<script lang="ts">
    let posts = $state<{ title: string; published: boolean }[]>([])

    // 僅取出 published 為 true 的貼文
    let publishedPosts = $derived(posts.filter(post => post.published))

    // 有多個依賴關係時
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `共${total}篇中已公開${published}篇`
    })
</script>

<p>{summary}</p>
```

`$derived` 會在依賴值變化時自動重新計算。相當於 Vue 的 `computed` 或 React 的 `useMemo`。

### `$effect` — 副作用處理

```svelte theme={null}
<script lang="ts">
    let query = $state('')

    // query 每次變化時執行
    $effect(() => {
        console.log('查詢字串變化了:', query)

        // 可回傳 cleanup 函式
        return () => {
            console.log('清理')
        }
    })
</script>

<input bind:value={query} placeholder="搜尋..." />
```

`$effect` 會在依賴的 `$state` 值變化時執行。相當於 React 的 `useEffect`，但不需要明確指定依賴陣列，使用過的 `$state` 變數會自動被追蹤。

***

## shadcn-svelte 元件

入門套件內建了 [shadcn-svelte](https://www.shadcn-svelte.com/)。shadcn-svelte 是與 React 版的 shadcn/ui 有相同設計思想的元件庫，可將程式碼複製到專案中自由客製化。

### 加入元件

```shell theme={null}
npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add input
npx shadcn-svelte@latest add card
```

執行指令後，元件的原始碼會被放到 `resources/js/components/ui/`。

### 使用方式

```svelte theme={null}
<script lang="ts">
    import { Button } from '@/components/ui/button'
    import { Input } from '@/components/ui/input'
    import * as Card from '@/components/ui/card'
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({ email: '', password: '' })

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/login')
    }
</script>

<Card.Root class="w-96">
    <Card.Header>
        <Card.Title>登入</Card.Title>
    </Card.Header>
    <Card.Content>
        <form onsubmit={submit} class="space-y-4">
            <Input
                type="email"
                bind:value={form.data.email}
                placeholder="電子郵件"
            />
            {#if form.errors.email}
                <p class="text-sm text-red-500">{form.errors.email}</p>
            {/if}

            <Input
                type="password"
                bind:value={form.data.password}
                placeholder="密碼"
            />

            <Button type="submit" disabled={form.processing} class="w-full">
                {form.processing ? '登入中...' : '登入'}
            </Button>
        </form>
    </Card.Content>
</Card.Root>
```

入門套件已內含 Button、Input、Card、Dialog、Dropdown 等常用元件。要加入其他元件時，隨時可用 `npx shadcn-svelte@latest add` 指令追加。

***

## 總結

Svelte 與 Laravel 的組合，特別是透過 Inertia 的「現代單體式」架構下能發揮實力。編譯器導向的設計使 bundle 尺寸較小，而 Runes 語法讓反應式變得明確、易懂為其特色。

| 元素                  | 角色                           |
| ------------------- | ---------------------------- |
| Laravel Controller  | 路由、資料取得、驗證                   |
| `Inertia::render()` | 從 Controller 傳資料到 Svelte 元件  |
| Svelte 頁面元件         | 以 `$props()` 接收 props 並渲染 UI |
| `useForm`           | 表單狀態管理、送出、錯誤顯示               |
| `Link` 元件           | 不重新整頁的頁面切換                   |
| `usePage().props`   | 存取共享資料                       |
| shadcn-svelte       | 標準元件庫                        |

使用 Inertia × Svelte 可獲得結合 Laravel 後端簡潔性與 Svelte 精簡撰寫風格的開發體驗。使用入門套件建立專案時，包含認證畫面在內都能立即開始開發。

<Card title="Inertia.js 官方文件" icon="book-open" href="https://inertiajs.com">
  Inertia v3 完整功能請參考官方文件。
</Card>


## Related topics

- [React 入門 — 搭配 Inertia × Laravel 使用的基礎知識](/zh-TW/blog/react-introduction.md)
- [Vue.js 入門 — 搭配 Inertia × Laravel 使用的基礎知識](/zh-TW/blog/vue-introduction.md)
- [認證入門](/zh-TW/authentication.md)
- [前端](/zh-TW/frontend.md)
- [開始學習 Laravel 前需要具備的知識](/zh-TW/true-tutorial.md)
