之前在 Day19 最後的總結有提到,頁面基本上都會按照它的檔案路徑而產生路由規則及使用對應 layout ,像是如果我的檔案是 /foo/+page.svelte 那原則上我會產生一個 URL 為 /foo 的頁面,且會使用到 /foo 以及 / 的 +layout.svelte (如果有的話)
但 SvelteKit 還是有提供一些特殊規則可以讓我們跳脫這些限制。
如果我今天想讓有兩個頁面想讓他們有共用的樣式以及驗證機制,當然我們可以直接將它們放在同一個資料夾裡然後再用 +layout.svelte 及 +layout.server.ts 達成這個功能。
但有時候我們就是繼續保持 URL 的語意,不想因為 layout 的共用影響到 URL 的階層,這時可以用 (route) 來協助我們共用 layout 但不會影響到 URL 的顯示。
以官方教學文件的例子來看,有兩個頁面想要登入後才能被查看分別是 /app 和 /account ,原本如果我想要共用 layout 我只能額外多一個 folder 存放他們 像是 authed/app 及 authed/account ,但如果改用 (authed) 後 authed 就不會出現在 URL 上了。
所以我們先新增兩個 +page.svelte
<!-- in day26/(authed)/account/+page.svelte -->
<h1>Account Page</h1>
<!-- in day26/(authed)/account/+page.svelte -->
<h1>APP Page</h1>
然後再新增要共用的 +layout.svelte 和 +layout.sever.ts
<!-- in day26/(authed)/+layout.svelte -->
<slot />
<form method="POST" action="/day26/logout" class="mt-12">
<button class="btn btn-warning btn-outline">log out</button>
</form>
action="/day26/logout指的是這個 form submit 時是去觸發/day26/logout/+page.server.ts的 default action
// in day26/(authed)/+layout.sever.ts
import { redirect, type ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = ({ cookies, url }) => {
if (!cookies.get('logged_in')) {
throw redirect(303, `/day26/login?redirectTo=${url.pathname}`);
}
};
<!-- in day26/+layout.svelte -->
<nav class="mb-4">
<a href="/day26/account">account</a>
<a href="/day26/app">app</a>
</nav>
<slot />
然後實作登入及登出的部分
<!-- in day26/login/+page.svelte -->
<form method="POST">
<button class="btn btn-primary btn-outline">log in</button>
</form>
// in day26/login/+page.svelte.sever.ts
import { redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions: Actions = {
default: ({ cookies, url }) => {
cookies.set('logged_in', 'true', { path: '/' });
throw redirect(303, url.searchParams.get('redirectTo') ?? '/day26/account');
}
};
// in day26/logout/+page.svelte.sever.ts
import { redirect } from '@sveltejs/kit';
export const actions = {
default: ({ cookies }) => {
cookies.delete('logged_in', { path: '/' });
throw redirect(303, '/day26/login');
}
};

可以發現當我在 Account 頁面時 URL 是顯示 http://localhost:5173/day26/account

那如果我們是想要特定的頁面中不要繼承 parent 的 layout,就可以使用 +page@.svelte 來讓這個頁面的佈局重置
以剛剛了例子來說,我在 day26/+layout.svelte 新增了一個 nav 來 redirect 頁面,但如果現在想要讓它不要出現在 login 頁面的話,可以將 day26/login/+page.svelte 改為 day26/login/+page@.svelte ,這樣子就不會使用到 day26/+layout.svelte

另外值得提的是 @ 是可以選擇要在哪個路由重置,假設我今天多了一個路由是 day26/(authed)/app/setting/+page.svelte 以及多了一個 day26/(authed)/app/+layout.svelte
<!-- day26/(authed)/app/setting/+page.svelte -->
<h1>Setting</h1>
<!-- day26/(authed)/app/+layout.svelte -->
<nav class="mb-4">
<a href="/day26/app/setting">setting</a>
</nav>
<slot />
預設情況下會繼承 routes/+layout.svelte 、 routes/day26/+layout.svelte 、 routes/day26/(authed)/+layout.svelte 以及 routes/day26/(authed)/app/+layout.svelte
如果今天只想顯示 routes/+layout.svelte 的我就是把檔名改成 +page@.svelte ,意即將這個 page 視為是在 root 的路徑,以此類推
如果我想顯示routes/+layout.svelte 、 routes/day26/+layout.svelte 就是將檔名改為 +page@day26.svelte
如果我想顯示routes/+layout.svelte 、 routes/day26/+layout.svelte 、 routes/day26/(authed)/+layout.svelte 就是將檔名改為 +page@(authed).svelte
+page.svelte

+page@(authed).svelte

+page@day26.svelte

+page@.svelte

所以唯一的限制就是 routes/+layout.svelte 一定會被用到,因為 @ 就是視為放到 routes 資料夾也就跟 routes/+layout.svelte 同層了。