先に結論だけ
ViteでエラーUncaught (in promise) SyntaxError: Unexpected token '<', "<html lang"... is not valid JSON
が発生したら、vite.config.ts
のappTypeオプションをmpa
に変更してください。Viteの開発サーバーが404エラーを返すようになります。どのパスに問題があるのかブラウザの開発者ツールから調査でき、対処が簡単になります。
はじめに
この記事は、Viteにおけるfetchに関連するエラー、およびViteサーバーのミドルウェアについて解説するものです。
対象とする読者
- HTML / CSS / JavaScriptの基礎的な知識がある
- Node.jsの基礎的な知識がある
- Viteを使用したことがあるが、学習初期である
この記事は、それぞれのツールのインストール方法などは解説しません。あらかじめご了承ください。
対象とする環境
- Vite v5.4.10
- Google Chrome v130.0.6723.92
この記事は、Vite v5.4での挙動について記載しています。将来のバージョンでは挙動が変わるかもしれないので、記事を読む前にお手元の環境のバージョンを確認してください。
問題の詳細
この記事で取り扱う問題の詳細と再現方法を説明します。
再現方法
最初に、ViteプロジェクトのpublicディレクトリにJSONファイルを配置します。次にindex.htmlからJSONファイルをfetchします。
const getJson = async () => {
const response = await fetch("./wrong_path.json");
const responseJson = await response.json();
}
その際、ファイルパスが誤っていると以下のエラーが発生します。
Uncaught (in promise) SyntaxError: Unexpected token '<', "<html lang"... is not valid JSON
パスが誤っているため、404エラーが返ってくると考えてしまいますが、実際にはJSONのパースエラーが発生します。
レスポンスの確認
ブラウザーの開発者ツールを使って、レスポンスを確認します。
開発者ツールのネットワークタブを開き、各ファイルのレスポンスを確認します。すると./wrong_path.json
へのリクエストに対してなぜかindex.html
の内容が返されています。
この問題は
- 誤ったパスでJSONファイルをリクエストする
- Viteサーバーが404エラーを返さず、
index.html
を返す - JavaScriptが
index.html
をJSONとしてパースしようとしてエラーが発生する
という流れで発生したことがわかりました。Unexpected token '<', "<html lang"
というエラーは、htmlファイルの先頭にある<html
タグをJSONとして解釈しようとし、失敗したこと示しています。
なぜViteは404エラーを返さないのか
Viteサーバーはなぜ404エラーを返さないのでしょうか?ソースコードから調査してみます。
SPA履歴フォールバックミドルウェア
Viteサーバーは本番環境を模倣するため、ミドルウェアによって挙動を変更できます。デフォルトで導入されるミドルウェアの1つにSPA履歴フォールバックミドルウェアがあります。このミドルウェアは、シングルページアプリケーションを配信することを念頭に、リクエストされたファイルが存在しない場合、index.html
を返します。
Viteのデフォルト設定では、SPA履歴フォールバックミドルウェアが有効になっています。
静的アセットはimportする
Viteは配信するアセットを最適化するため、importすることを推奨しています。開発環境、本番環境を問わず、アセットをimportすることでViteが最適化を行い、開発者は実際のアセットがどこに配置されているかを気にする必要がありません。
importパスが誤っている場合、Viteはビルド時にエラーを出力します。ビルドされたサイトには誤ったパスが含まれないことが保証されます。
Viteの設計思想
- Viteはデフォルトでシングルページアプリケーションを配信するように設定されている
- 静的アセットはimportされているため、ファイルパスが誤っていることは想定されていない
この2つの理由から、Viteサーバーはデフォルトでは404エラーを返さず、index.html
を返します。
解決策
なぜViteが404エラーを返さないのか、その理由を理解したところで、解決策を考えていきます。
404エラーを返す方法
Viteサーバーが404エラーを返すようにするには、vite.config.ts
のappTypeオプションをmpa
に変更します。
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
...
appType: "mpa",
...
});
この設定はマルチページアプリケーションを配信するためのものです。その結果SPA履歴フォールバックミドルウェアが無効になり、Viteサーバーが404エラーを返すようになります。ブラウザーの開発者ツールやコンソールでエラーを確認し、問題のあるパスを特定できます。
サーバー内に配置するアセットは明示的URLでインポートする
Viteには明示的なURLのインポートという機能があります。この機能では、アセットパスがViteによって変更されず、ファイル名にハッシュ値が追加されたり、インライン化されないことを保証します。この機能は本来rollup.jsでバンドルできないWebAssemblyファイルを扱うためのものですが、publicフォルダー内のファイルをインポートする際にも利用できます。
import jsonURL from "/correctPath.json?url";
const getJson = async () => {
const response = await fetch(jsonURL);
const responseJson = await response.json();
}
URLインポートはビルド時に評価されるため、ファイルパスが誤っているとエラーが発生します。これによりクライアントエラーを待たず開発時にエラーを検知できます。
publicディレクトリのURLは絶対パスで指定する
publicディレクトリのファイルは絶対パスで指定しなければいけません。相対パスで指定すると、Viteはファイルパスを解決できずにビルド時にエラーを出力します。
import jsonURL from "/correctPath.json?url"; // 👌 OK "/"で始まる絶対パス
import jsonURL from "./correctPath.json?url"; // 🚫 NG "./"で始まる相対パス
個人的な感想
Viteは最低限の設定で一般的な開発パターンを実現することを理念としています。設計を理解していくと使いやすいツールですが、学習初期は戸惑うこともあります。
個人的にViteは今後も発展すると思うので、学習する価値があります。
参考記事