Astro tsx パスがサブディレクトリで壊れる原因とbase設定での直し方
この記事には広告リンクを含みます。紹介している商品・サービスの一部はアフィリエイトプログラムを利用しています。 商品・サービスの選定はご自身の判断でお願いいたします。
Astroで作ったサイトをサブディレクトリ配下にデプロイした瞬間、画像が消えてリンクが壊れた経験はないだろうか。base設定を追加しても、tsxコンポーネント内のパスはそのまま残るケースがある。
原因の核心は「.astroファイルとtsxコンポーネントでパス解決の挙動が違う」点にある。
自分が初めてAstroのクライアント案件を受けたとき、約2週間の制作終盤にこの問題で詰まった。dist手動パッチで急場を凌ぎ、その後base設定で根本解決に至った経緯と判断基準をまとめる。
.astroとtsxでパス解決が分かれる仕組み
Astroは.astroファイルをコンパイルする際、src属性やhref属性の絶対パスにbaseプレフィックスを自動付与する。astro.config.mjsにbase: '/subdir/'と設定すれば、.astro内のパスは一括で書き換えられる。
問題になるのは、tsxコンポーネント(Reactをclient:*ディレクティブで使うケース)だ。tsxはAstroコンパイラではなくReactのJSXランタイムが処理するため、内部の文字列リテラルにAstroは手を入れない。
つまり、tsx内に/images/hero.jpgと書いてあれば、ビルド後もそのまま出力される。
サブディレクトリが/subdir/なら、ブラウザは/images/hero.jpgにアクセスして404になる。
次のコードが典型的な壊れパターンだ。
// NG: サブディレクトリデプロイで壊れる
const HeroSection = () => (
<div>
<img src="'/images/hero.jpg'" alt='ヒーロー' />
<a href="'/about/'>詳細はこちら</a>"
</div>
);ViteはAstroのアセット変換処理を介して.astroを処理するが、tsxはReactランタイムとして別ルートで実行される。
この2つのパス解決を「同じAstroのビルド処理」と混同すると、設定を変えても直らない状況が続く。.astroファイル内の同じ書き方はbaseが効くが、このtsx内のsrcとhrefはそのまま残る。
これが「base設定を入れたのに一部のパスだけ壊れた」という状況の正体だ。
base設定で直る範囲とtsx内の修正手順
astro.config.mjsのbase設定は次のように書く。
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
export default defineConfig({
base: '/subdir/',
integrations: [react()],
});この設定で.astroファイル内の絶対パスは自動修正される。tsxコンポーネント内は修正されないため、import.meta.env.BASE_URLを使って動的にパスを生成する。
// OK: BASE_URLを使った動的パス生成
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
const HeroSection = () => (
<div>
<img src="{`${base}/images/hero.jpg`}" alt='ヒーロー' />
<a href="{`${base}/about/`}>詳細はこちら</a>"
</div>
);import.meta.env.BASE_URLはビルド時にbase値で置き換わる。
dev環境では/になり、本番ビルドでは/subdir/になるため、dev/build両方で動作する。
複数のtsxコンポーネントにまたがる場合は、base値を返すユーティリティをまとめておくと変更に強い。
// src/utils/base.ts
export const BASE = import.meta.env.BASE_URL.replace(/\/$/, '');末尾スラッシュの扱いはプロジェクトの方針に合わせて揃えておくとリンク切れが起きにくい。
サードパーティ選択肢としてastro-relative-linksプラグインがある。
これはビルド後のHTMLを走査して相対パスに変換するため、tsx内の静的パスも修正される。
ただし、JavaScriptで動的生成するURLには効かない。
静的ページメインでJSから動的にURLを組み立てる処理がない構成なら、プラグイン一本で対処できる。
Reactコンポーネントで動的リンクを使っている場合はimport.meta.env.BASE_URLの方が確実だ。
プラグイン選定の参考として実務でよく使うおすすめプラグイン一覧も確認してほしい。
dev/buildの差異確認と手動パッチの使いどころ
Astroのdev serverはbaseを反映するが、ビルド後とは挙動が細部で異なる。
dev環境で問題なく見えていたのに、astro build後のプレビューで初めてパス壊れが出るケースがある。tsx内の文字列リテラルはdev serverが補正しているように見えても、ビルド後は素のパスで出力されるためだ。
確認は次の順で進めるのが確実だ。
astro.config.mjsにbaseを設定してdev serverで動作確認するastro build後にastro previewでローカルプレビューする- 本番のサブディレクトリと同一パスで
npx serve distなどを使って最終確認する
手動パッチ(ビルド後のdist/ファイルを直接書き換える)を選ぶ判断基準は次のとおりだ。
手段 | 対象範囲 | 注意点 |
|---|---|---|
base設定 + BASE_URL参照 | .astro + tsx両方 | tsx側は個別修正が必要 |
astro-relative-links | 静的HTML属性のみ | JS動的URLには効かない |
dist手動パッチ | ビルド後の任意ファイル | 再ビルドで消える、緊急時のみ |
手動パッチは再ビルドのたびに同じ処理が必要で、CI/CDがある構成ではミスが起きやすい。
自分の案件でもこのパターンで詰まり、急ぎのリリース後にbase設定+tsx修正で対応したが、想定外の箇所で修正漏れが1箇所出た。
ビルド後のプレビューで全パスが通るか確認する工程は省かない方がいい。
緊急リリース後は必ずbase設定+tsx修正に切り替えることを前提に判断する。
Astroの技術選定の経緯についてはAstroとWordPressの使い分け:迷わない3つの判断基準も参考にしてほしい。
.astroファイルとtsxコンポーネントではパス解決の担当が異なる- base設定は
.astroの静的パスに効くが、tsx内の文字列リテラルには効かない tsx内ではimport.meta.env.BASE_URLを使って動的にパスを生成する- dev環境と
astro build後で挙動が変わるため、必ずbuild後のプレビューで確認する - 手動パッチは緊急時のみ、リリース後に根本修正する前提で使う
コーディング代行・実装判断で詰まったら Build に振ってください。