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

<Warning>Đây là tài liệu Inertia.js v1, phiên bản không còn được duy trì tích cực. Vui lòng tham khảo [tài liệu v3](/v3/getting-started/index).</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 2 icon="vuejs" theme={null}
  <template>
      <Layout>
          <Head title="Welcome" />
          <h1>Welcome</h1>
          <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
      </Layout>
  </template>

  <script>
  import Layout from './Layout'
  import { Head } from '@inertiajs/vue2'

  export default {
      components: {
          Head,
          Layout,
      },
      props: {
          user: Object,
      },
  }
  </script>
  ```

  ```vue Vue 3 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>
      )
  }
  ```

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

      export let user
  </script>

  <Layout>
      <svelte:head>
          <title>Welcome</title>
      </svelte:head>
      <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 trang bằng cách trả về một [response Inertia](/v1/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 một ứ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
        ]);
    }
}
```

## Tạo layout

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

<CodeGroup>
  ```vue Vue 2 icon="vuejs" theme={null}
  <template>
      <main>
          <header>
              <a href="/">Home</a>
              <a href="/about">About</a>
              <a href="/contact">Contact</a>
          </header>
          <article>
              <slot />
          </article>
      </main>
  </template>

  <script>
  import { Link } from '@inertiajs/vue2'

  export default {
      components: {
          Link,
      }
  }
  </script>
  ```

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

  <template>
      <main>
          <header>
              <a href="/">Home</a>
              <a href="/about">About</a>
              <a href="/contact">Contact</a>
          </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>
                  <a href="/">Home</a>
                  <a href="/about">About</a>
                  <a href="/contact">Contact</a>
              </header>
              <article>{children}</article>
          </main>
      )
  }
  ```

  ```html Svelte 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>
  ```
</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 2 icon="vuejs" theme={null}
  <template>
      <div>
          <H1>Welcome</H1>
          <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
      </div>
  </template>

  <script>
  import Layout from './Layout'

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

      // Using shorthand syntax...
      layout: Layout,

      props: {
          user: Object,
      },
  }
  </script>
  ```

  ```vue Vue 3 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
  ```

  ```html Svelte 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>
  ```
</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 2 icon="vuejs" theme={null}
  <template>
      <div>
          <H1>Welcome</H1>
          <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
      </div>
  </template>

  <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 shorthand syntax...
      layout: [SiteLayout, NestedLayout],

      props: {
          user: Object,
      },
  }
  </script>
  ```

  ```vue Vue 3 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
  ```

  ```html Svelte icon="s" theme={null}
  <script context="module">
      import SiteLayout from './SiteLayout.svelte'
      import NestedLayout from './NestedLayout.svelte'
      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>
  ```
</CodeGroup>

<VueSpecific>
  Nếu đang dùng Vue 2.7 hoặc Vue 3, bạn cũng có thể dùng [plugin defineOptions](https://vue-macros.sxzz.moe/macros/define-options.html) để định nghĩa layout bên trong `<script setup>`:

  ```html 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 2 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 Vue 3 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 2 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 Vue 3 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 }
          return page
      },
      // ...
  })
  ```
</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 v1 chính thức](https://inertiajs.com/docs/v1/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.
