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

# Trang

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

<Warning>Bạn đang xem tài liệu Inertia.js v2. Inertia.js v3 đã được phát hành và hiện là phiên bản mặc định. Hãy xem [hướng dẫn nâng cấp](/v3/getting-started/upgrade-guide) để bắt đầu.</Warning>

Khi xây dựng ứng dụng bằng Inertia, mỗi trang trong ứng dụng thường có controller / route riêng cùng một component JavaScript tương ứng. Nhờ đó, bạn chỉ cần truy xuất dữ liệu cần thiết cho trang đó — không cần API.

Ngoài ra, toàn bộ dữ liệu mà trang cần có thể được truy xuất trước khi trình duyệt render trang, qua đó loại bỏ nhu cầu hiển thị trạng thái "đang tải" khi người dùng truy cập ứng dụng.

## Tạo trang

Trang Inertia đơn giản chỉ là các component JavaScript. Nếu từng viết component Vue, React hoặc Svelte, bạn sẽ thấy rất quen thuộc. Như ví dụ bên dưới, các trang nhận dữ liệu từ controller của ứng dụng thông qua props.

<CodeGroup>
  ```vue Vue icon="vuejs" theme={null}
  <script setup>
  import Layout from './Layout'
  import { Head } from '@inertiajs/vue3'

  defineProps({ user: Object })
  </script>

  <template>
      <Layout>
          <Head title="Welcome" />
          <h1>Welcome</h1>
          <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
      </Layout>
  </template>
  ```

  ```jsx React icon="react" theme={null}
  import Layout from './Layout'
  import { Head } from '@inertiajs/react'

  export default function Welcome({ user }) {
      return (
          <Layout>
              <Head title="Welcome" />
              <h1>Welcome</h1>
              <p>Hello {user.name}, welcome to your first Inertia app!</p>
          </Layout>
      )
  }
  ```

  ```svelte Svelte 4 icon="s" theme={null}
  <script>
      import Layout from './Layout.svelte'

      export let user
  </script>

  <svelte:head>
      <title>Welcome</title>
  </svelte:head>

  <Layout>
      <h1>Welcome</h1>
      <p>Hello {user.name}, welcome to your first Inertia app!</p>
  </Layout>
  ```

  ```svelte Svelte 5 icon="s" theme={null}
  <script>
      import Layout from './Layout.svelte'

      let { user } = $props()
  </script>

  <svelte:head>
      <title>Welcome</title>
  </svelte:head>

  <Layout>
      <h1>Welcome</h1>
      <p>Hello {user.name}, welcome to your first Inertia app!</p>
  </Layout>
  ```
</CodeGroup>

<ClientSpecific>
  Với trang ở trên, bạn có thể render bằng cách trả [response Inertia](/v2/the-basics/responses) từ controller hoặc route. Trong ví dụ này, giả sử trang được lưu tại <VueSpecific>`resources/js/Pages/User/Show.vue`</VueSpecific><ReactSpecific>`resources/js/Pages/User/Show.jsx`</ReactSpecific><SvelteSpecific>`resources/js/Pages/User/Show.svelte`</SvelteSpecific> trong ứng dụng Laravel.
</ClientSpecific>

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

class UserController extends Controller
{
    public function show(User $user)
    {
        return Inertia::render('User/Show', [
            'user' => $user
        ]);
    }
}
```

Nếu cố render một trang không tồn tại, response thường là màn hình trống. Để ngăn điều này, bạn có thể đặt config `inertia.ensure_pages_exist` thành `true`. Adapter Laravel khi đó sẽ ném `Inertia\ComponentNotFoundException` nếu không tìm thấy trang.

## Tạo layout

Mặc dù không bắt buộc, với phần lớn dự án, việc tạo layout component dùng chung cho mọi trang là hợp lý. Bạn có thể nhận thấy trong ví dụ trên nội dung trang được bọc bằng component `<Layout>`. Sau đây là ví dụ về component như vậy:

<CodeGroup>
  ```vue Vue icon="vuejs" theme={null}
  <script setup>
  import { Link } from '@inertiajs/vue3'
  </script>

  <template>
      <main>
          <header>
              <Link href="/">Home</Link>
              <Link href="/about">About</Link>
              <Link href="/contact">Contact</Link>
          </header>
          <article>
              <slot />
          </article>
      </main>
  </template>
  ```

  ```jsx React icon="react" theme={null}
  import { Link } from '@inertiajs/react'

  export default function Layout({ children }) {
      return (
          <main>
              <header>
                  <Link href="/">Home</Link>
                  <Link href="/about">About</Link>
                  <Link href="/contact">Contact</Link>
              </header>
              <article>{children}</article>
          </main>
      )
  }
  ```

  ```svelte Svelte 4 icon="s" theme={null}
  <script>
      import { inertia } from '@inertiajs/svelte'
  </script>

  <main>
      <header>
          <a use:inertia href="/">Home</a>
          <a use:inertia href="/about">About</a>
          <a use:inertia href="/contact">Contact</a>
      </header>
      <article>
          <slot />
      </article>
  </main>
  ```

  ```svelte Svelte 5 icon="s" theme={null}
  <script>
      import { inertia } from '@inertiajs/svelte'

      let { children } = $props()
  </script>

  <main>
      <header>
          <a use:inertia href="/">Home</a>
          <a use:inertia href="/about">About</a>
          <a use:inertia href="/contact">Contact</a>
      </header>
      <article>
          {@render children()}
      </article>
  </main>
  ```
</CodeGroup>

<ClientSpecific>
  Như bạn thấy, template này không có gì đặc thù riêng của Inertia. Đây chỉ là một component <VueSpecific>Vue</VueSpecific><ReactSpecific>React</ReactSpecific><SvelteSpecific>Svelte</SvelteSpecific> thông thường.
</ClientSpecific>

## Layout duy trì xuyên suốt

Mặc dù triển khai layout dưới dạng phần tử con của page component rất đơn giản, cách này khiến instance của layout bị hủy và tạo lại giữa các lần chuyển trang. Điều đó có nghĩa là bạn không thể duy trì state của layout khi điều hướng giữa các trang.

Ví dụ, có thể website podcast của bạn có một trình phát âm thanh mà bạn muốn tiếp tục phát khi người dùng điều hướng trong website. Hoặc đơn giản bạn muốn giữ vị trí cuộn của thanh điều hướng sidebar giữa các lần chuyển trang. Trong những trường hợp này, giải pháp là sử dụng persistent layout của Inertia.

<CodeGroup>
  ```vue Vue icon="vuejs" theme={null}
  <script>
  import Layout from './Layout'

  export default {
      // Using a render function...
      layout: (h, page) => h(Layout, [page]),

      // Using shorthand syntax...
      layout: Layout,
  }
  </script>

  <script setup>
  defineProps({ user: Object })
  </script>

  <template>
      <h1>Welcome</h1>
      <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
  </template>
  ```

  ```jsx React icon="react" theme={null}
  import Layout from './Layout'

  const Home = ({ user }) => {
      return (

              <H1>Welcome</H1>
              <p>Hello {user.name}, welcome to your first Inertia app!</p>

      )
  }

  Home.layout = page => <Layout children={page} title="Welcome" />

  export default Home
  ```

  ```svelte Svelte 4 icon="s" theme={null}
  <script context="module">
      export { default as layout } from './Layout.svelte'
  </script>

  <script>
      export let user
  </script>

  <h1>Welcome</h1>
  <p>Hello {user.name}, welcome to your first Inertia app!</p>
  ```

  ```svelte Svelte 5 icon="s" theme={null}
  <script module>
      export { default as layout } from './Layout.svelte'
  </script>

  <script>
      let { user } = $props()
  </script>

  <h1>Welcome</h1>
  <p>Hello {user.name}, welcome to your first Inertia app!</p>
  ```
</CodeGroup>

Bạn cũng có thể tạo các cách bố trí layout phức tạp hơn bằng nested layout.

<CodeGroup>
  ```vue Vue icon="vuejs" theme={null}
  <script>
  import SiteLayout from './SiteLayout'
  import NestedLayout from './NestedLayout'

  export default {
      // Using a render function...
      layout: (h, page) => {
          return h(SiteLayout, () => h(NestedLayout, () => page))
      },

      // Using the shorthand...
      layout: [SiteLayout, NestedLayout],
  }
  </script>

  <script setup>
  defineProps({ user: Object })
  </script>

  <template>
      <h1>Welcome</h1>
      <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
  </template>
  ```

  ```jsx React icon="react" theme={null}
  import SiteLayout from './SiteLayout'
  import NestedLayout from './NestedLayout'

  const Home = ({ user }) => {
      return (

              <H1>Welcome</H1>
              <p>Hello {user.name}, welcome to your first Inertia app!</p>

      )
  }

  Home.layout = page => (
      <SiteLayout title="Welcome">
          <NestedLayout children={page} />
      </SiteLayout>
  )

  export default Home
  ```

  ```svelte Svelte 4 icon="s" theme={null}
  <script context="module">
      import SiteLayout from './SiteLayout.svelte'
      import NestedLayout from './NestedLayout.svelte'

      // Using a render function...
      export const layout = (h, page) => {
          return h(SiteLayout, [h(NestedLayout, [page])])
      }

      // Using the shorthand...
      export const layout = [SiteLayout, NestedLayout]
  </script>

  <script>
      export let user
  </script>

  <h1>Welcome</h1>
  <p>Hello {user.name}, welcome to your first Inertia app!</p>
  ```

  ```svelte Svelte 5 icon="s" theme={null}
  <script module>
      import SiteLayout from './SiteLayout.svelte'
      import NestedLayout from './NestedLayout.svelte'

      // Using a render function...
      export const layout = (h, page) => {
          return h(SiteLayout, [h(NestedLayout, [page])])
      }

      // Using the shorthand...
      export const layout = [SiteLayout, NestedLayout]
  </script>

  <script>
      let { user } = $props()
  </script>

  <h1>Welcome</h1>
  <p>Hello {user.name}, welcome to your first Inertia app!</p>
  ```
</CodeGroup>

<VueSpecific>
  Nếu dùng Vue 3.3+, bạn có thể dùng [defineOptions](https://vuejs.org/api/sfc-script-setup.html#defineoptions) để định nghĩa layout bên trong `<script setup>`. Các phiên bản Vue cũ hơn có thể dùng [plugin defineOptions](https://vue-macros.sxzz.moe/macros/define-options.html):

  ```vue theme={null}
  <script setup>
  import Layout from './Layout'
  defineOptions({ layout: Layout })
  </script>
  ```
</VueSpecific>

## Layout mặc định

Nếu sử dụng persistent layout, bạn có thể thấy thuận tiện khi định nghĩa layout mặc định của trang trong callback `resolve()` ở file JavaScript chính của ứng dụng.

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  import Layout from './Layout'

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
          let page = pages[`./Pages/${name}.vue`]
          page.default.layout = page.default.layout || Layout
          return page
      },
      // ...
  })
  ```

  ```js React icon="react" theme={null}
  import Layout from './Layout'

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
          let page = pages[`./Pages/${name}.jsx`]
          page.default.layout = page.default.layout || (page => <Layout children={page} />)
          return page
      },
      // ...
  })
  ```

  ```js Svelte icon="s" theme={null}
  import Layout from './Layout'

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.svelte', { eager: true })
          let page = pages[`./Pages/${name}.svelte`]
          return { default: page.default, layout: page.layout || Layout }
      },
      // ...
  })
  ```
</CodeGroup>

Cách này sẽ tự động đặt layout của trang thành `Layout` nếu trang đó chưa được thiết lập layout.

Bạn thậm chí có thể đi xa hơn bằng cách thiết lập layout mặc định có điều kiện dựa trên `name` của trang, vốn có sẵn trong callback `resolve()`. Ví dụ, bạn có thể không muốn áp dụng layout mặc định cho các trang công khai.

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  import Layout from './Layout'

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
          let page = pages[`./Pages/${name}.vue`]
          page.default.layout = name.startsWith('Public/') ? undefined : Layout
          return page
      },
      // ...
  })
  ```

  ```js React icon="react" theme={null}
  import Layout from './Layout'

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
          let page = pages[`./Pages/${name}.jsx`]
          page.default.layout = name.startsWith('Public/') ? undefined : page => <Layout children={page} />
          return page
      },
      // ...
  })
  ```

  ```js Svelte icon="s" theme={null}
  import Layout from './Layout'

  createInertiaApp({
      resolve: name => {
          const pages = import.meta.glob('./Pages/**/*.svelte', { eager: true })
          let page = pages[`./Pages/${name}.svelte`]
          return { default: page.default, layout: name.startsWith('Public/') ? undefined : Layout }
      },
      // ...
  })
  ```
</CodeGroup>

***

## 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 v2 chính thức](https://inertiajs.com/docs/v2/the-basics/pages). 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.
