【SvelteKit】結局、アセット(画像)はどこに配置するのか【「ローカルなアセット」というアイデア】
まとめ
- Svelteのアセット管理は、
import
文を介した記述が望ましい- Viteのビルトイン・ハンドリングで良しなに処理される
- 「ローカルなアセット」になるよう、
$lib
配下への配置は避ける- 各Svelteファイルと同じディレクトリに配置するとよい
背景
当団(Orchestra Canvas Tokyo)では、ホームページやブログをSvelteKitを用いて開発しています。
小規模ながらのサイトの開発・保守を続ける中で、SvelteKitにおいて 「ローカルなアセット」 という概念が有用なのではないか?という私見が生まれました。
今回はそれについてまとめていきます。
私が調べた限りでは類似の意見を見つけきれなかったのですが、もし心あたりがある方がいらっしゃいましたら、教えていただけると嬉しいです!
基本のアセット管理
まずは、基本のアセット管理について述べます。
SvelteKitのアセット管理の基本をまとめなおしたものですので、すでに慣れている方は、本題に迫る「ローカルなアセット」まで飛ばしてください。
公式ドキュメントには、2通りの管理方法が記載されています。
すなわち、/static
への配置と、Vite[1]のビルトイン・ハンドリングを用いる方法です。
それぞれについて述べ、その後に比較検討を行います。
/static
直接配信されるべきアセット、すなわちファビコンなどは /static
に配置することとなっています。
ここに配置することで、ビルド後も /static
をドキュメントルートに見立てたパス関係で配信されるようになります。
例えば、/static/favicon.ico
は example.com/favicon.ico
として配信されるようになります。
favicon.ico
や robots.txt
など、おまじない的ファイルの配置に向けた使用が主ではありますが、ソースコードからの参照も可能です。
例えば、次のような形です。
<script>
let { children } = $props();
</script>
<header>
<!-- /static/logo.svg を配置しているものとする -->
<a href="/"><img src="/logo.svg" alt="Orchestra Canvas Tokyo"></a>
</header>
{@render children()}
基本の +layout.svelte
[2]をベースに、リンク付きロゴ画像を貼り付けたものです。
SvelteKitやViteを介することなく、HTMLの基本的な構文として、ルートからの相対パスで画像が指定されています。
Viteのビルトイン・ハンドリング
しかし、画像のベストプラクティス[3]として、ドキュメントには「Viteのビルトイン・ハンドリング」を用いる方法が紹介されています。
これに則って先のコードを修正すると、次のような形になります。
<script>
// 先ほどとは異なり、/src/routes/logo.svg を配置しているものとする
import logo from './logo.svg'; // 相対パス指定となっていることに注意
let { children } = $props();
</script>
<header>
<a href="/"><img src={logo} alt="Orchestra Canvas Tokyo"></a>
</header>
{@render children()}
これも問題なく動作しますが、Viteによる処理を介しています。
Viteのドキュメント[4]によると、次のような処理が行われます。
- ファイル名の変更:
logo.xxxxxxx.png
とハッシュ様の文字列が挟まれる - 一定サイズ[5]以下である場合、インライン化される
- 指定ディレクトリ(デフォルトでは
/_assets
)へ配置される
これらの処理により、キャッシュによる更新遅延の回避、読み込みパフォーマンスの改善などが期待されます。
使い分け
基本的にはViteのビルトイン・ハンドリング、すなわち import
文を介した記述が優先されると考えます。
先述したように、パフォーマンスの利点があるためです。
これから述べる、「ローカルなアセット」という考え方でも、Viteのビルトイン・ハンドリングの利用を前提としています。
しかし、次の要素のいずれかを満たす場合、/static
フォルダへの配置が必要です。
- 決められたパス・ファイル名が必要なもの
- 例)
favicon.ico
robots.txt
- 例)
- Svelte上で参照されないもの
- 例)
- CSSで
@import
されるフォントファイル - HTMLメールなどから直接参照されるファイル
- CSSで
- ただし、CSSでの
url()
インポートはViteでハンドリングされる
- 例)
ローカルなアセット
多くの場合、Viteのビルトイン・ハンドリングが有用ということは分かりました。
では、アセットを実際にどこに配置するのかが問題となります。
主な選択肢は次の2つです。
- インポートするSvelteファイルと同じディレクトリへ配置する
-
$lib
配下[6]に配置する
私は、前者「インポートするSvelteファイルと同じディレクトリへの配置」が第一選択であると考えます。
これこそが、「ローカルなアセット」という考え方です。
これの考え方を推す理由が2つあります。
すなわち、
-
+page.svelte
の登場 - 依存関係の単純化
です。
+page.svelte
の登場
現行のSvelteではルーティングにおいて、+page.svelte
が用いられます[7]。
この仕様により、各エンドポイントがディレクトリ分けされています。
すなわち、各エンドポイントを構成するファイル群をまとめて管理することができます。
プレフィックス +
も効いており、エクスプローラ上でSvelte関連のファイルが最上部に表示されることが多くなります。
特に、IDEのファイルエクスプローラではファイル名昇順で並ぶため、このメリットを享受しやすいです。
プロジェクト構成の一例を次に示します。
/src/
└ routes/
└ about-us/
├ +page.svelte
├ logo.svg
├ photo.jpg
└ sponsor.png
/src/routes/about-us/+page.svelte
で使うファイル群を /src/routes/about-us/
に配置しています。
ちょうどディレクトリがアセットのスコープのように機能しており、アセットの依存関係が一目瞭然ではないでしょうか!
依存関係の単純化
配置場所のもう1つの選択肢である、「$lib
配下への配置」は、依存関係を複雑化させる懸念があります。
つまり、複数のSvelteファイルから参照される、いわば「グローバル・アセット」が氾濫する危険があるということです。
ちょうど、変数はスコープが小さいほうが良いように、アセットもスコープを小さくするのが好ましいと考えています。
/src/
└ lib/
└ assets/
└ image/
├ logo-large.png
├ logo-small.png
├ sponsor-brand.png
├ x-brand.svg
└ ……
とはいえ、IDEのワークスペース横断検索を行えば、実際にどのファイルから参照されているかは分かるので、致命的な問題とはなりません。
しかし、「グローバル変数」があまり好まれないのと同様の理由で、「グローバル・アセット」も避けられてしかるべきではないでしょうか。
「$lib
配下への配置」に付随する問題として、$lib
配下のフォルダ・ファイル管理が煩雑になることも挙げられます。
実用上のポイント
「ローカルなアセット」の運用に際し、いくつかポイントがあります。
複数ファイルからインポートされるアセット
複数ファイルからインポートされるアセットがあるとき、ローカルなアセットでは問題が生じるのではないか!
というのは、真っ先に想定されうる懸念です。
つまり、ディレクトリ構造で明示したかったスコープを壊しながら、長い相対パスでインポートしたり、同じファイルを複数個所に配置してパフォーマンスの懸念を生じさせる必要があったりするのではないかということです。
これに対し、$lib/assets/
などで共通ライブラリとしてアセットを用意し、そこに配置するというのが消極的な解決方法でしょう。
しかし、コンポーネントの設計を見直すような「積極的な解決」を図ることで、多くの場合は「ローカルなアセット」を維持できます。
そのため、実際には共通ライブラリとして用意するような解決方法が必要となる場合は、非常に稀であると考えています。
例えば、ヘッダーとフッターに同じロゴ画像を用いる場合を考えます。
消極的な解決方法では、次のようなプロジェクト構成になります。
/src/
├ lib/
│├ Footer.svelte
│├ Header.svelte
│└ assets/
│ └ logo.svg
└ routes/
└ +layout.svelte
<script>
import Header from '$lib/Header.svelte';
import Footer from '$lib/Footer.svelte';
let { children } = $props();
</script>
<Header />
{@render children()}
<Footer />
例えば、次のような構成が考えられます。
/src/
├ lib/
│└ layout/
│ ├ Footer.svelte
│ ├ Header.svelte
│ └ logo.svg
└ routes/
└ +layout.svelte
前述した +
プレフィックスによるフォルダ内の見通しの良さは失われますが、アセットのスコープが明確となる点は魅力的ではないでしょうか。
より複雑なユースケースでは、ロゴ画像をプロパティとして渡すのも手かもしれません。
いわば、「ロゴ画像を外から注入」します。
今回のシンプルな例では必ずしも適当ではありませんが、次のようなイメージです。
/src/
├ lib/
│├ Footer.svelte
│└ Header.svelte
└ routes/
├ +layout.svelte
└ logo.svg
<script>
import Header from '$lib/Header.svelte';
import Footer from '$lib/Footer.svelte';
import logo from './logo.svg';
</script>
<Header {logo} />
{@render children()}
<Footer {logo} />
おわりに
ここまでお読みいただきありがとうございました!
この「ローカルなアセット」という考え方、お察しかと思いますが、コンポーネントにも応用できそうです。
その詳細についてはまた来週にでも……
次回演奏会のご案内
Orchestra Canvas Tokyoは、都内を中心に活動するアマチュアオーケストラです。
日々の癒しに、新たなひらめきのきっかけに——
オーケストラの演奏会はいかがでしょうか?
初めての方も大歓迎!
ご来場をお待ちしています。
Orchestra Canvas Tokyo
第14回定期演奏会2025年7月12日(土)
練馬区立練馬文化センター 大ホール
シューマン / 交響曲第2番 ほか詳細はチケット販売ページにて
-
Introduction / Dynamic attributes • Svelte Tutorial。
同ページでは@sveltejs/enhanced-imgやCDNを選択肢に入れて、ベストプラクティスについて議論されている。
この議論については、本記事の要点とはずれるため割愛する。 ↩︎ -
サイズ閾値はビルドオプションの
build.assetsInlineLimit
で指定される。
詳細はビルドオプション | Viteに記述あり。 ↩︎ -
Svelteのビルトイン・エイリアスで、
/src/lib
を指します。 ↩︎ -
かつては、
index.svelte
を用いるか、エンドポイント名.svelte
を作成していました。 ↩︎
Discussion