SvelteKitのコードを読む — Nav.svelte・+layout.svelte編
.svelte拡張子の意味
.svelteは単なるファイル名ではなく、Svelteコンパイラが認識する特別な形式。
Profile.ts → TypeScriptファイル。UIは作れない
Profile.svelte → Svelteコンポーネント。HTML + CSS + TSを1ファイルに書ける
ただし「SvelteKitがルーティングに使うかどうか」はファイル名と場所で決まる:
src/routes/+page.svelte → URLに対応するページとして認識される
src/components/Nav.svelte → ただのコンポーネント(URLとは無関係)
Nav.svelte を読む
ブロック① インポート
import { resolve } from '$app/paths'
// → URLを正しく解決するSvelteKit組み込み関数
import { page } from '$app/state'
// → 現在のページ情報(URL等)をリアクティブに取得
import * as DarkMode from 'svelte-fancy-darkmode'
// → * as は「全部まとめてDarkModeという名前で取り込む」
import MoonToSunny from '~icons/line-md/moon-filled-to-sunny-filled-loop-transition'
import SunnyToMoon from '~icons/line-md/sunny-filled-loop-to-moon-filled-transition'
// → unplugin-iconsでアイコンをコンポーネントとして使うimport * asはまとめてimportする書き方:
// 通常の書き方
import { ToggleButton, Header } from 'svelte-fancy-darkmode'
// * as でまとめて取り込む
import * as DarkMode from 'svelte-fancy-darkmode'
DarkMode.ToggleButton // このようにアクセスする
DarkMode.Headerブロック② {#snippet} — 再利用できるHTMLの断片
{#snippet underline(isPath: boolean, transparentDefault = false)}
<span
class={{
'bg-accent-100': isPath, // isPathがtrueならオレンジ下線
'bg-transparent': !isPath, // falseなら透明
}}
></span>
{/snippet}{#snippet}はSvelte5の新機能で関数に近い概念:
引数: isPath(現在のページかどうか)
処理: trueならオレンジ、falseなら透明なspanを返す
呼び出すときは{@render}を使う:
{@render underline(page.url.pathname === '/')}
// → 現在のパスが / なら isPath=true → オレンジ下線が表示されるブロック③ リアクティブなURL監視
<div class={{ hidden: page.url.pathname === '/' }}>
@irom999
</div>page.url.pathnameはページ遷移のたびに自動更新される:
/ にいるとき → page.url.pathname === '/' が true → @irom999が非表示
/blog にいるとき → page.url.pathname === '/' が false → @irom999が表示
Reactで同じことを書くとusePathname()フックが必要:
// React
const pathname = usePathname()
<div className={pathname === '/' ? 'hidden' : ''}>
@irom999
</div>Svelteはpageを参照するだけで自動的にリアクティブになる。
ブロック④ ダークモードボタン
<DarkMode.ToggleButton>
{#snippet dark()}
<SunnyToMoon /> ← ダークモード時に表示するアイコン
{/snippet}
{#snippet light()}
<MoonToSunny /> ← ライトモード時に表示するアイコン
{/snippet}
</DarkMode.ToggleButton>2つのsnippetをpropsとして渡している。どのアイコンを表示するかは親(Nav.svelte)が決め、切り替えのロジックはDarkMode側が持つ設計。
+layout.svelte のHTMLを読む
<main max-w-4xl mxa my3 px-8 un-dark>
<Nav />
{#key page.url}
{@render children()}
{/key}
</main><main> の属性
<main max-w-4xl mxa my3 px-8 un-dark>
<!-- 最大幅制限 中央寄せ 上下余白 左右余白 ダークモード -->全部UnoCSSのAttributify。CSSクラスをHTMLの属性として書いている。
<Nav />
Nav.svelteコンポーネントをここに配置。+layout.svelteは全ページ共通なので、ナビゲーションが全ページに表示される。
{#key page.url} — 値が変わるたびに作り直す
{#key page.url}
{@render children()}
{/key}{#key}は値が変わるたびに中身を完全に破棄して作り直すSvelteの構文:
/ にアクセス → page.url が変わる → children()を作り直す
/blog に遷移 → page.url が変わる → children()を作り直す
/bio に遷移 → page.url が変わる → children()を作り直す
これがないとページ遷移時にView Transitionsアニメーションが正しく動かない。
{@render children()} — ページの中身を差し込む
{@render children()}children()は各+page.svelteの内容。+layout.svelteはここに各ページを差し込む:
/ にアクセス時:
<main>
<Nav />
+page.svelte の中身 ← children()
</main>
/blog にアクセス時:
<main>
<Nav />
blog/+page.svelte の中身 ← children()
</main>
FlutterのNavigatorで言う「画面の中身が切り替わる場所」に相当する。
まとめ
| 構文・機能 | 説明 |
|---|---|
import * as X |
全エクスポートをXという名前でまとめてimport |
{#snippet} |
再利用できるHTMLの断片(引数を取れる) |
{@render} |
snippetを呼び出して表示する |
page.url.pathname |
現在のURLをリアクティブに取得 |
{#key 値} |
値が変わるたびに中身を作り直す |
{@render children()} |
子ページの内容をここに差し込む |