> ## Documentation Index
> Fetch the complete documentation index at: https://inertiajs-vi.tuantq.online/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Side Rendering (SSR)

export const VueSpecific = ({children}) => {
  const [code, setCode] = useState(() => {
    if (typeof window === "undefined") {
      return "Vue";
    }
    return localStorage.getItem("code")?.replace(/"/g, "") || "Vue";
  });
  useEffect(() => {
    const handler = event => {
      if (event.detail?.key === "code") {
        setCode(event.detail.value?.replace(/"/g, ""));
      }
    };
    window.addEventListener("localStorageUpdate", handler);
    return () => window.removeEventListener("localStorageUpdate", handler);
  }, []);
  if (code !== "Vue") {
    return null;
  }
  return children;
};

export const SvelteSpecific = ({children}) => {
  const [code, setCode] = useState(() => {
    if (typeof window === "undefined") {
      return null;
    }
    return localStorage.getItem("code")?.replace(/"/g, "") || null;
  });
  useEffect(() => {
    const handler = event => {
      if (event.detail?.key === "code") {
        setCode(event.detail.value?.replace(/"/g, ""));
      }
    };
    window.addEventListener("localStorageUpdate", handler);
    return () => window.removeEventListener("localStorageUpdate", handler);
  }, []);
  if (!code?.includes("Svelte")) {
    return null;
  }
  return children;
};

export const ReactSpecific = ({children}) => {
  const [code, setCode] = useState(() => {
    if (typeof window === "undefined") {
      return null;
    }
    return localStorage.getItem("code")?.replace(/"/g, "") || null;
  });
  useEffect(() => {
    const handler = event => {
      if (event.detail?.key === "code") {
        setCode(event.detail.value?.replace(/"/g, ""));
      }
    };
    window.addEventListener("localStorageUpdate", handler);
    return () => window.removeEventListener("localStorageUpdate", handler);
  }, []);
  if (code !== "React") {
    return null;
  }
  return children;
};

export const ClientSpecific = ({children}) => {
  const [nada, setNada] = useState();
  return children;
};

Server-side rendering render trước các trang JavaScript trên máy chủ, nhờ đó người truy cập nhận được HTML đã được render đầy đủ khi truy cập ứng dụng. Vì ứng dụng phục vụ HTML hoàn chỉnh, các công cụ tìm kiếm cũng dễ index website hơn.

Server-side rendering dùng Node.js để render page trong background process; do đó server phải có Node để SSR hoạt động đúng. SSR server của Inertia yêu cầu Node.js 22 trở lên.

## Laravel Starter Kit

Nếu đang dùng [Laravel Starter Kits](https://laravel.com/docs/starter-kits), Inertia SSR được [hỗ trợ](https://laravel.com/docs/starter-kits#inertia-ssr) thông qua một build command:

```bash theme={null}
npm run build:ssr
```

## Thiết lập Vite Plugin

Cách được khuyến nghị để cấu hình SSR là dùng [`@inertiajs/vite` plugin](/v3/installation/client-side-setup#installation). Cách này tự động xử lý cấu hình SSR, bao gồm SSR ở development mode mà không cần Node.js server riêng.

<Steps>
  <Step title="Install the Vite plugin">
    ```bash theme={null}
    npm install @inertiajs/vite
    ```
  </Step>

  <Step title="Configure Vite">
    Thêm Inertia plugin vào file `vite.config.js`. Plugin tự động phát hiện SSR entry point.

    ```js vite.config.js theme={null}
    import inertia from '@inertiajs/vite'
    import laravel from 'laravel-vite-plugin'
    import { defineConfig } from 'vite'

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

    Bạn cũng có thể cấu hình SSR option rõ ràng.

    ```js vite.config.js theme={null}
    inertia({
        ssr: {
            entry: 'resources/js/ssr.js',
            port: 13714,
            host: '127.0.0.1',
            cluster: true,
        },
    })
    ```

    Bạn có thể truyền `false` để tắt xử lý SSR tự động của plugin, ví dụ khi muốn [cấu hình SSR thủ công](#manual-setup) hoặc [tắt hoàn toàn SSR](#disabling-ssr).

    ```js vite.config.js theme={null}
    inertia({
        ssr: false,
    })
    ```
  </Step>

  <Step title="Update your build script">
    Cập nhật script `build` trong `package.json` để build cả hai bundle.

    ```json package.json theme={null}
    "scripts": {
        "dev": "vite",
       "build": "vite build" // [!code --]
       "build": "vite build && vite build --ssr" // [!code ++]
    },
    ```
  </Step>
</Steps>

### Development mode

Vite plugin tự động xử lý SSR trong development. Không cần build SSR bundle hay chạy Node.js server riêng. Chỉ cần chạy Vite dev server như bình thường:

```bash theme={null}
npm run dev
```

Vite plugin expose server endpoint để Laravel dùng cho rendering, có đầy đủ hỗ trợ HMR.

### Production

Trong production, build cả hai bundle rồi khởi động SSR server.

```bash theme={null}
npm run build
php artisan inertia:start-ssr
```

### Clustering

Mặc định SSR server chạy một thread. Bạn có thể bật clustering để chạy nhiều Node server trên cùng port, request được xử lý bởi từng thread theo round-robin.

```js vite.config.js theme={null}
inertia({
    ssr: {
        cluster: true,
    },
})
```

### Host

Mặc định SSR server bind `0.0.0.0`, nên truy cập được từ mọi network interface. Bạn có thể giới hạn vào interface cụ thể bằng tùy chọn `host`.

```js vite.config.js theme={null}
inertia({
    ssr: {
        host: '127.0.0.1',
    },
})
```

## Thiết lập thủ công

Vite plugin mặc định tái sử dụng entry point `app.js` cho SSR nên không cần file riêng. Hầu hết tùy chỉnh có thể xử lý bằng [`withApp` callback](/v3/installation/client-side-setup#customizing-the-app).

Để kiểm soát nhiều hơn, chẳng hạn cung cấp [callback `setup` thủ công](/v3/installation/client-side-setup#manual-setup), bạn có thể tạo entry point `resources/js/ssr.js` riêng và cập nhật `app.js` để dùng [client-side hydration](#client-side-hydration).

### SSR Entry Point

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  import { createInertiaApp } from '@inertiajs/vue3'
  import createServer from '@inertiajs/vue3/server'
  import { createSSRApp, h } from 'vue'
  import { renderToString } from 'vue/server-renderer'

  createServer(page =>
      createInertiaApp({
          page,
          render: renderToString,
          resolve: name => {
              const pages = import.meta.glob('./Pages/**/*.vue')
              return pages[`./Pages/${name}.vue`]()
          },
          setup({ App, props, plugin }) {
              return createSSRApp({
                  render: () => h(App, props),
              }).use(plugin)
          },
      }),
  )
  ```

  ```jsx React icon="react" theme={null}
  import { createInertiaApp } from '@inertiajs/react'
  import createServer from '@inertiajs/react/server'
  import ReactDOMServer from 'react-dom/server'

  createServer(page =>
      createInertiaApp({
          page,
          render: ReactDOMServer.renderToString,
          resolve: name => {
              const pages = import.meta.glob('./Pages/**/*.jsx')
              return pages[`./Pages/${name}.jsx`]()
          },
          setup: ({ App, props }) => <App {...props} />,
      }),
  )
  ```

  ```js Svelte icon="s" theme={null}
  import { createInertiaApp } from '@inertiajs/svelte'
  import createServer from '@inertiajs/svelte/server'
  import { render } from 'svelte/server'

  createServer(page =>
      createInertiaApp({
          page,
          resolve: name => {
              const pages = import.meta.glob('./Pages/**/*.svelte')
              return pages[`./Pages/${name}.svelte`]()
          },
          setup({ App, props }) {
              return render(App, { props })
          },
      }),
  )
  ```
</CodeGroup>

Hãy thêm vào `app.js` bất kỳ phần nào còn thiếu nhưng hợp lý khi chạy SSR, chẳng hạn plugin hoặc custom mixin.

### Client-side hydration

<ClientSpecific>
  Bạn cũng nên cập nhật `app.js` để dùng hydration thay vì render bình thường. Điều này cho phép <VueSpecific>Vue</VueSpecific><ReactSpecific>React</ReactSpecific><SvelteSpecific>Svelte</SvelteSpecific> tiếp quản HTML đã server-render và làm nó tương tác được mà không re-render.
</ClientSpecific>

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  import { createApp, h } from 'vue' // [!code --]
  import { createSSRApp, h } from 'vue' // [!code ++]
  import { createInertiaApp } from '@inertiajs/vue3'

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.vue')
          return pages[`./Pages/${name}.vue`]()
      },
      setup({ el, App, props, plugin }) {
      createApp({ render: () => h(App, props) }) // [!code --]
      createSSRApp({ render: () => h(App, props) }) // [!code ++]
          .use(plugin)
          .mount(el)
      },
  })
  ```

  ```js React icon="react" theme={null}
  import { createInertiaApp } from '@inertiajs/react'
  import { createRoot } from 'react-dom/client' // [!code --]
  import { hydrateRoot } from 'react-dom/client' // [!code ++]

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.jsx')
          return pages[`./Pages/${name}.jsx`]()
      },
      setup({ el, App, props }) {
          createRoot(el).render(<App {...props} />) // [!code --]
          hydrateRoot(el, <App {...props} />) // [!code ++]
      },
  })
  ```

  ```js Svelte icon="s" theme={null}
  import { createInertiaApp } from '@inertiajs/svelte'
  import { mount } from 'svelte' // [!code --]
  import { hydrate, mount } from 'svelte' // [!code ++]

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.svelte')
          return pages[`./Pages/${name}.svelte`]()
      },
      setup({ el, App, props }) {
          mount(App, { target: el, props }) // [!code --]
          if (el.dataset.serverRendered === 'true') { // [!code ++:5]
              hydrate(App, { target: el, props })
          } else {
              mount(App, { target: el, props })
          }
      },
  })
  ```
</CodeGroup>

### Không dùng xử lý tự động của Vite Plugin

Bạn có thể truyền `ssr: false` cho Inertia plugin để tắt xử lý SSR tự động và tự quản lý SSR build. Bạn cũng nên thêm thuộc tính `ssr` vào cấu hình Laravel Vite plugin để nó biết entry point.

<CodeGroup>
  ```js Vue icon="vuejs" vite.config.js theme={null}
  export default defineConfig({
      plugins: [
          laravel({
              input: ['resources/js/app.js'],
              ssr: 'resources/js/ssr.js', // [!code ++]
              refresh: true,
          }),
          inertia({
              ssr: false, // [!code ++]
          }),
      ],
  })
  ```

  ```js React icon="react" vite.config.js theme={null}
  export default defineConfig({
      plugins: [
          laravel({
              input: ['resources/js/app.jsx'],
              ssr: 'resources/js/ssr.jsx', // [!code ++]
              refresh: true,
          }),
          inertia({
              ssr: false, // [!code ++]
          }),
      ],
  })
  ```

  ```js Svelte icon="s" vite.config.js theme={null}
  export default defineConfig({
      plugins: [
          laravel({
              input: ['resources/js/app.js'],
              ssr: 'resources/js/ssr.js', // [!code ++]
              refresh: true,
          }),
          inertia({
              ssr: false, // [!code ++]
          }),
      ],
  })
  ```
</CodeGroup>

### Clustering

Bạn có thể truyền tùy chọn `cluster` vào `createServer` để chạy nhiều Node server trên cùng port, request được phân phối round-robin.

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  createServer(page =>
      createInertiaApp({
          // ...
      }),
      { cluster: true },
  )
  ```

  ```jsx React icon="react" theme={null}
  createServer(page =>
      createInertiaApp({
          // ...
      }),
      { cluster: true },
  )
  ```

  ```js Svelte icon="s" theme={null}
  createServer(page =>
      createInertiaApp({
          // ...
      }),
      { cluster: true },
  )
  ```
</CodeGroup>

### Host

Mặc định SSR server bind `0.0.0.0`, truy cập được từ mọi interface. Bạn có thể truyền tùy chọn `host` vào `createServer` để giới hạn vào interface cụ thể.

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  createServer(page =>
      createInertiaApp({
          // ...
      }),
      { host: '127.0.0.1' },
  )
  ```

  ```jsx React icon="react" theme={null}
  createServer(page =>
      createInertiaApp({
          // ...
      }),
      { host: '127.0.0.1' },
  )
  ```

  ```js Svelte icon="s" theme={null}
  createServer(page =>
      createInertiaApp({
          // ...
      }),
      { host: '127.0.0.1' },
  )
  ```
</CodeGroup>

## Chạy SSR server

<Note>SSR server chỉ bắt buộc trong production. Trong development, [Vite plugin](#development-mode) tự động xử lý SSR.</Note>

Sau khi build cả client-side và server-side bundle, bạn có thể khởi động SSR server bằng Artisan command sau.

```bash theme={null}
php artisan inertia:start-ssr
```

Mặc định SSR server dùng `node` làm runtime. Bạn có thể đổi bằng tùy chọn `runtime` trong `config/inertia.php`. Path tuyệt đối tới runtime binary cũng được hỗ trợ.

```php theme={null}
'ssr' => [
    'runtime' => env('INERTIA_SSR_RUNTIME', 'node'),
],
```

Flag `--runtime` trên Artisan command override giá trị cấu hình cho một lần gọi.

```bash theme={null}
php artisan inertia:start-ssr --runtime=bun
```

Bạn cũng có thể bật `ensure_runtime_exists` để kiểm tra runtime binary tồn tại trước khi khởi động SSR server. Command sẽ thoát với lỗi nếu không tìm thấy binary.

```php theme={null}
'ssr' => [
    'ensure_runtime_exists' => (bool) env('INERTIA_SSR_ENSURE_RUNTIME_EXISTS', false),
],
```

Khi server đang chạy, bạn có thể truy cập app trong trình duyệt với SSR đã bật. Thậm chí có thể tắt hoàn toàn JavaScript mà vẫn điều hướng được trong ứng dụng.

## Xử lý lỗi

Khi SSR render thất bại, Inertia graceful fallback về client-side rendering. Vite plugin ghi thông tin lỗi chi tiết ra console, gồm component name, request URL, source location và hint phù hợp để xử lý.

Các lỗi SSR phổ biến được tự động phân loại. Browser API error như tham chiếu `window` hoặc `document` trong server-rendered code đi kèm hướng dẫn chuyển code vào lifecycle hook. Component resolution error gợi ý kiểm tra file path và casing.

Inertia cũng dispatch event `SsrRenderFailed` phía server. Bạn có thể lắng nghe event để log lỗi hoặc gửi tới error tracking service.

```php theme={null}
use Illuminate\Support\Facades\Log;
use Inertia\Ssr\SsrRenderFailed;

Event::listen(SsrRenderFailed::class, function (SsrRenderFailed $event) {
    Log::warning('SSR failed', $event->toArray());
});
```

### Ném exception khi lỗi

Vì Inertia graceful fallback về client-side rendering, lỗi SSR có thể không được chú ý. Test vẫn pass vì client-side render thành công nhưng user không bao giờ nhận server-rendered HTML. Điều này đặc biệt phổ biến trong E2E test bằng Laravel Dusk hoặc Pest Browser Testing.

Bạn có thể đặt tùy chọn `throw_on_error` trong `config/inertia.php` để ném exception thay vì fallback im lặng, giúp phát hiện vấn đề SSR sớm.

```php theme={null}
'ssr' => [
    'throw_on_error' => (bool) env('INERTIA_SSR_THROW_ON_ERROR', false),
],
```

<Warning>Không khuyến nghị tùy chọn này trong production vì lỗi SSR sẽ trả error response thay vì fallback về client-side rendering.</Warning>

Bạn có thể đặt environment variable trong `phpunit.xml` để chỉ bật trong testing.

```xml theme={null}
<env name="INERTIA_SSR_THROW_ON_ERROR" value="true"/>
```

## Tắt SSR

SSR có hai lớp: **Vite plugin** phục vụ SSR trong development và build SSR bundle cho production, còn **Laravel adapter** dispatch rendering request tới SSR server. Để tắt hoàn toàn SSR, nên tắt cả hai.

```js vite.config.js theme={null}
inertia({
    ssr: false,
})
```

```php config/inertia.php theme={null}
'ssr' => [
    'enabled' => false,
],
```

Bạn cũng có thể ngăn Laravel adapter dispatch SSR request bằng lập trình qua phương thức `Inertia::disableSsr()`. Điều này hữu ích khi muốn giữ SSR trong build nhưng [tắt khi test](/v3/advanced/testing#disabling-ssr-during-tests) hoặc trong môi trường cụ thể.

```php theme={null}
use Inertia\Inertia;

Inertia::disableSsr();
```

Có thể truyền boolean hoặc closure để tắt SSR theo điều kiện.

```php theme={null}
Inertia::disableSsr(app()->runningUnitTests());

Inertia::disableSsr(fn () => app()->runningUnitTests());
```

## Loại route khỏi SSR

Đôi khi bạn muốn bỏ qua server-side rendering cho một số route trong khi vẫn bật SSR cho phần còn lại của ứng dụng.

### Qua Middleware

Bạn có thể dùng property `$withoutSsr` trên Inertia middleware để tắt SSR cho các route pattern cụ thể.

```php theme={null}
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    /**
     * Defines the routes that should not use SSR.
     *
     * @var array<int, string>
     */
    protected $withoutSsr = [
        'admin/*',
        'dashboard',
    ];
}
```

### Qua Facade

Bạn cũng có thể loại các route cụ thể bằng phương thức `Inertia::withoutSsr()`, thường được gọi từ service provider.

```php theme={null}
use Inertia\Inertia;

Inertia::withoutSsr(['admin/*', 'dashboard']);
```

### Theo từng request

Bạn có thể tắt SSR cho request hiện tại bằng cách đặt cấu hình `inertia.ssr.enabled` thành `false`.

```php theme={null}
if (request()->is('admin/*')) {
    config(['inertia.ssr.enabled' => false]);
}
```

## Triển khai

Khi deploy app có SSR lên production, bạn cần build cả client-side bundle (`app.js`) và server-side bundle (`ssr.js`), sau đó chạy SSR server như background process, thường bằng process monitor như Supervisor.

```bash theme={null}
php artisan inertia:start-ssr
```

Để dừng SSR server, chẳng hạn khi triển khai phiên bản mới của website, bạn có thể dùng Artisan command `inertia:stop-ssr`. Công cụ giám sát tiến trình (như Supervisor) cần chịu trách nhiệm tự động khởi động lại SSR server sau khi nó dừng.

```bash theme={null}
php artisan inertia:stop-ssr
```

Bạn có thể dùng Artisan command `inertia:check-ssr` để xác minh SSR server đang chạy. Điều này hữu ích sau khi deploy và rất phù hợp làm Docker health check để đảm bảo server phản hồi đúng như mong đợi.

```bash theme={null}
php artisan inertia:check-ssr
```

Mặc định, hệ thống sẽ kiểm tra server-side bundle tồn tại trước khi gửi request tới SSR server. Trong một số trường hợp, chẳng hạn ứng dụng chạy trên nhiều server hoặc trong container, web server có thể không truy cập được SSR bundle. Để tắt kiểm tra này, đặt giá trị cấu hình `inertia.ssr.ensure_bundle_exists` thành `false`.

### Laravel Cloud

Để chạy SSR server trên Laravel Cloud, bạn có thể dùng [hỗ trợ Inertia SSR nguyên bản của Cloud](https://cloud.laravel.com/docs/compute#inertia-ssr).

### Laravel Forge

Để chạy SSR server trên Forge, bạn có thể bật nó bằng [tùy chọn Inertia SSR](https://forge.laravel.com/docs/sites/laravel#inertia-server-side-rendering-ssr) trong panel ứng dụng của site. Forge sẽ tạo daemon cần thiết và, nếu bạn chọn, cập nhật deploy script để khởi động lại SSR server sau mỗi lần deploy.

***

## Tài liệu chính thức

Bài dịch này được đối chiếu từ [tài liệu Inertia.js v3 chính thức](https://inertiajs.com/docs/v3/advanced/server-side-rendering). Nếu có khác biệt do phiên bản hoặc cập nhật mới, hãy ưu tiên tài liệu chính thức làm nguồn tham chiếu.
