tiangolo/fastapi · error · RuntimeError

Frontend fallback file '{fallback}' does not exist in direct

Error message

Frontend fallback file '{fallback}' does not exist in directory '{directory}'. Resolved absolute directory: '{resolved_directory}'

What it means

At construction time, `_FrontendStaticFiles.__init__` calls `_check_fallback_file` (routing.py:1919-1929) when `check_dir` is true and `fallback` is `"index.html"` or `"404.html"`. It stats the file via `lookup_path`; if missing or not a regular file, it raises `RuntimeError`. This guarantees the configured fallback page actually exists before the app serves traffic.

Source

Thrown at fastapi/routing.py:1925

        self.fallback = fallback
        if check_dir and not os.path.isdir(directory):
            raise RuntimeError(
                f"Frontend directory '{directory}' does not exist. "
                f"Resolved absolute path: '{_get_resolved_absolute_path(directory)}'"
            )
        super().__init__(
            directory=directory,
            html=True,
            check_dir=check_dir,
            follow_symlink=False,
        )
        if check_dir and fallback in {"index.html", "404.html"}:
            self._check_fallback_file(fallback)

    def _check_fallback_file(self, fallback: str) -> None:
        _, stat_result = self.lookup_path(fallback)
        if stat_result is None or not stat.S_ISREG(stat_result.st_mode):
            raise RuntimeError(
                f"Frontend fallback file '{fallback}' does not exist in "
                f"directory '{self.directory}'. Resolved absolute directory: "
                f"'{self._get_resolved_directory()}'"
            )

    def _get_resolved_directory(self) -> str:
        assert self.directory is not None
        return _get_resolved_absolute_path(self.directory)

    def get_path(self, scope: Scope) -> str:
        path = _get_fastapi_scope(scope).get(_FASTAPI_FRONTEND_PATH_KEY, "")
        assert isinstance(path, str)
        return os.path.normpath(os.path.join(*path.split("/")))

    async def get_response_for_scope(self, scope: Scope) -> Response:
        if not self.config_checked:
            await self.check_config()
            self.config_checked = True

View on GitHub (pinned to 42a41db11f)

Solutions

  1. Ensure the named fallback file exists directly under `directory` at startup.
  2. Point `directory` at the folder that actually contains `index.html`/`404.html`.
  3. Use `fallback="auto"` so FastAPI uses whichever fallback file is present (or none).
  4. Use `fallback=None` to disable fallback entirely.

Example fix

# before
app.frontend("/", directory="dist", fallback="index.html")  # dist/index.html missing

# after
app.frontend("/", directory="dist", fallback="auto")
Defensive patterns

Strategy: validation

Validate before calling

import os, stat
from pathlib import Path

def assert_fallback_file(directory: str, fallback: str | None) -> None:
    if fallback in {"index.html", "404.html"}:
        p = Path(directory) / fallback
        if not (p.is_file() and stat.S_ISREG(os.stat(p).st_mode)):
            raise FileNotFoundError(f"Fallback file missing: {p}")

# usage
assert_fallback_file(dist, "index.html")
app.frontend("/", directory=dist, fallback="index.html")

Type guard

import os

def fallback_file_present(directory: str, fallback: str | None) -> bool:
    return fallback in {None, "auto"} or os.path.isfile(os.path.join(directory, fallback))

Prevention

When it happens

Trigger: `app.frontend("/", directory="dist", fallback="index.html")` when `dist/index.html` does not exist at startup (and check_dir is active). Same for `fallback="404.html"` without `dist/404.html`.

Common situations: Frontend build emits files under a nested folder (e.g. `dist/build/index.html`) but you point at `dist`; SPA that has no 404 page but you force `fallback="404.html"`; building in a different mode that skips index generation.

Related errors


AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04). Data as JSON: /data/errors/08273329afed413f.json. Report an issue: GitHub.