ข้ามไปยังเนื้อหา

Layouts และ Navigation

ใน React Router คุณสร้าง layout ด้วยการวาง <Outlet /> ไว้ใน parent route component — child route ใดก็ตามจะ render ตรงที่คุณวาง outlet ไว้ SvelteKit ใช้แนวทางจาก file system แทน: ไฟล์ +layout.svelte จะ wrap ทุก route ในไดเรกทอรีเดียวกันให้อัตโนมัติ คุณได้พฤติกรรม nesting แบบเดียวกัน แต่ไม่ต้องตั้งค่า route config ใด ๆ เลย เหมือนกับ Next.js App Router, layout ของ SvelteKit จะ persist ข้ามการนำทาง — ไม่ unmount และ remount เมื่อคุณนำทางระหว่าง child route

รันตัวอย่างนี้ในโปรเจกต์ SvelteKit (npx sv create)

ใน React Router คุณระบุจุดที่ children จะ render ด้วยการวาง <Outlet /> ใน SvelteKit Svelte 5 layout ใช้ snippet syntax {@render children()} — ไม่มี <slot /> ใน Svelte 5 layout

React
// src/layouts/RootLayout.jsx (React Router v6)
import { Outlet, Link } from 'react-router-dom';
export default function RootLayout() {
return (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/blog">Blog</Link>
</nav>
<main>
<Outlet />
</main>
</div>
);
}
Svelte
<!-- src/routes/+layout.svelte -->
<script>
let { children } = $props();
</script>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
<main>
{@render children()}
</main>

{@render children()} คือจุดที่ child route ที่ตรงกันจะ render เข้ามา children snippet ถูกส่งมาให้อัตโนมัติโดย SvelteKit — คุณแค่ destructure มันออกมาจาก $props()

React Router ให้ useLocation() เพื่ออ่าน URL ปัจจุบันและคำนวณ active style SvelteKit เปิดเผยข้อมูลเดียวกันผ่าน object page ที่ import จาก $app/state

React
// React Router — active link with useLocation
import { Link, useLocation } from 'react-router-dom';
const links = [
{ to: '/', label: 'Home' },
{ to: '/about', label: 'About' },
{ to: '/blog', label: 'Blog' },
];
export default function Nav() {
const { pathname } = useLocation();
return (
<nav>
{links.map(({ to, label }) => (
<Link
key={to}
to={to}
className={pathname === to ? 'active' : ''}
>
{label}
</Link>
))}
</nav>
);
}
Svelte
<!-- +layout.svelte (Svelte 5) -->
<script>
import { page } from '$app/state';
let { children } = $props();
const links = [
{ href: '/', label: 'Home' },
{ href: '/about', label: 'About' },
{ href: '/blog', label: 'Blog' },
];
</script>
<nav>
{#each links as { href, label }}
<a {href} class:active={page.url.pathname === href}>
{label}
</a>
{/each}
</nav>
<main>
{@render children()}
</main>
<style>
a { margin-right: 1rem; text-decoration: none; color: inherit; }
a.active { font-weight: bold; border-bottom: 2px solid currentColor; }
</style>

page จาก $app/state เป็น reactive object — มันอัปเดตทุกครั้งที่มีการนำทางโดยอัตโนมัติ ไม่ต้องมี useEffect ไม่ต้อง setup subscription

ใน React คุณต้องใช้ component <Link> (react-router หรือ next/link) เพื่อ client-side navigation — <a> ธรรมดาจะทำให้เกิด full page reload SvelteKit ไม่ต้องการสิ่งเหล่านั้นเลย: ใช้ <a href> ธรรมดาได้เลย แล้ว SvelteKit จะ intercept การคลิกและทำ client-side navigation ให้อัตโนมัติ ไม่ต้อง import ไม่ต้องมี wrapper component

page จาก $app/state เป็น reactive object ที่เปิดเผยทุกอย่างเกี่ยวกับ route ปัจจุบัน — เทียบเท่ากับ useLocation() + useParams() ใน React Router หรือ usePathname() + useSearchParams() ใน Next.js โดยตรง

<script>
import { page } from '$app/state';
</script>
<p>Current path: {page.url.pathname}</p>
<p>URL params: {JSON.stringify(page.params)}</p>
<p>Search: {page.url.search}</p>

property สำคัญที่ page ให้:

Propertyเทียบเท่าใน React
page.url.pathnameuseLocation().pathname / usePathname()
page.paramsuseParams()
page.url.searchParamsuseSearchParams()
page.dataข้อมูลที่ load function ส่งกลับมา
page.statusHTTP status code ของหน้านี้

นี่คือ layout แบบสมบูรณ์ที่คุณนำไปใช้ในโปรเจกต์ SvelteKit ใดก็ได้:

src/routes/+layout.svelte
<script>
import { page } from '$app/state';
let { children } = $props();
</script>
<header>
<a href="/" class="logo">MySite</a>
<nav>
<a href="/" class:active={page.url.pathname === '/'}>Home</a>
<a href="/about" class:active={page.url.pathname === '/about'}>About</a>
<a href="/blog" class:active={page.url.pathname.startsWith('/blog')}>Blog</a>
</nav>
</header>
<main>
{@render children()}
</main>
<footer>© 2026 MySite</footer>
<style>
header {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.logo { font-weight: 700; text-decoration: none; }
nav { display: flex; gap: 1rem; margin-left: auto; }
nav a { text-decoration: none; color: inherit; }
nav a.active { font-weight: 600; color: #FF3E00; }
</style>

Layout ซ้อนกันได้ตามลำดับชั้นไดเรกทอรี — เหมือนกับ Next.js App Router ทุกประการ

flowchart TB
  routes(["src/routes/"])
  routes --> rootLayout["+layout.svelte (root layout — nav + footer)"]
  routes --> rootPage["+page.svelte → /"]
  routes --> dashboard["dashboard/"]
  dashboard --> dashLayout["+layout.svelte (dashboard layout — sidebar)"]
  dashboard --> dashPage["+page.svelte → /dashboard"]
  dashboard --> settings["settings/"]
  settings --> settingsPage["+page.svelte → /dashboard/settings"]
Nested layouts in SvelteKit

/dashboard/settings จะได้รับทั้ง root layout และ dashboard layout ซ้อนกัน โดยไม่ต้องกำหนด config ใด ๆ เพิ่มเติม

  • +layout.svelte wrap ทุก route ในไดเรกทอรีเดียวกัน — และไดเรกทอรีลูกจะสืบทอดมันอัตโนมัติ
  • Children render ที่ {@render children()} (Svelte 5 snippet syntax) ไม่มี <slot /> ใน Svelte 5 layout
  • Layout ซ้อนกันได้: dashboard/+layout.svelte wrap ทุก route ภายใต้ /dashboard ซ้อนอยู่ภายใน root layout
  • ใช้ <a href="/about"> ธรรมดา — ไม่ต้องมี component <Link> SvelteKit intercept การคลิก <a> และทำ client-side navigation ให้อัตโนมัติ
  • page จาก $app/state ให้ url, params, data, route.id และอื่น ๆ — reactive ทั้งหมด
  • page.url.pathname คือวิธีมาตรฐานในการทำ active-link logic
  • Layout persist ข้ามการนำทางระหว่าง child route ของมัน — ไม่ unmount เหมือนกับ layout ของ Next.js App Router
ใน SvelteKit Svelte 5 layout child route render ที่ไหน?
คุณอ่าน pathname ปัจจุบันใน SvelteKit component ได้อย่างไร?
คุณต้องการ client-side navigation ไปหน้า /dashboard ใน SvelteKit ควรเขียนอย่างไร?