{"record":{"id":"765a7fe6af914667","repo":"fastapi/fastapi","slug":"frontend-fallback-file-fallback-does-not-exist","errorCode":null,"errorMessage":"Frontend fallback file '{fallback}' does not exist in directory '{self.directory}'. Resolved absolute directory: '{self._get_resolved_directory()}'","messagePattern":"Frontend fallback file '(.+?)' does not exist in directory '(.+?)'\\. Resolved absolute directory: '(.+?)'","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"fastapi/routing.py","lineNumber":1925,"sourceCode":"        self.fallback = fallback\n        if check_dir and not os.path.isdir(directory):\n            raise RuntimeError(\n                f\"Frontend directory '{directory}' does not exist. \"\n                f\"Resolved absolute path: '{_get_resolved_absolute_path(directory)}'\"\n            )\n        super().__init__(\n            directory=directory,\n            html=True,\n            check_dir=check_dir,\n            follow_symlink=False,\n        )\n        if check_dir and fallback in {\"index.html\", \"404.html\"}:\n            self._check_fallback_file(fallback)\n\n    def _check_fallback_file(self, fallback: str) -> None:\n        _, stat_result = self.lookup_path(fallback)\n        if stat_result is None or not stat.S_ISREG(stat_result.st_mode):\n            raise RuntimeError(\n                f\"Frontend fallback file '{fallback}' does not exist in \"\n                f\"directory '{self.directory}'. Resolved absolute directory: \"\n                f\"'{self._get_resolved_directory()}'\"\n            )\n\n    def _get_resolved_directory(self) -> str:\n        assert self.directory is not None\n        return _get_resolved_absolute_path(self.directory)\n\n    def get_path(self, scope: Scope) -> str:\n        path = _get_fastapi_scope(scope).get(_FASTAPI_FRONTEND_PATH_KEY, \"\")\n        assert isinstance(path, str)\n        return os.path.normpath(os.path.join(*path.split(\"/\")))\n\n    async def get_response_for_scope(self, scope: Scope) -> Response:\n        if not self.config_checked:\n            await self.check_config()\n            self.config_checked = True","sourceCodeStart":1907,"sourceCodeEnd":1943,"githubUrl":"https://github.com/fastapi/fastapi/blob/a1fa70d4237d50aae6586a0d9b229df583463d21/fastapi/routing.py#L1907-L1943","documentation":"FastAPI's frontend-serving route (e.g. app.mount or the SPA-style static route) was configured with check_dir=True and an explicit fallback of 'index.html' or '404.html', but that file does not exist as a regular file inside the configured directory. The check runs eagerly in _FrontendStaticFiles.__init__ via _check_fallback_file (fastapi/routing.py:1920-1929), so the app fails at construction time, not at request time. The message reports both the configured directory and its resolved absolute path to help spot wrong working directories or missing build output.","triggerScenarios":"Calling the frontend route helper with directory='./frontend/dist', fallback='index.html' (or '404.html'), check_dir=True, when 'frontend/dist/index.html' is absent (frontend not built, file named differently, or directory resolved relative to a different CWD). Only fires for fallback values 'index.html' and '404.html'; 'auto' and None skip the check (routing.py:1919).","commonSituations":"Serving an SPA where the JS build step (npm run build / vite build) was never run or output went to a different folder; CI pipelines that start the API before building the frontend; Docker images that copy the wrong dist directory; relative directory paths resolved against an unexpected working directory.","solutions":["Build the frontend so the fallback file exists in the configured directory (e.g. run 'npm run build' and confirm dist/index.html is present).","Verify the resolved absolute directory printed in the error message; fix the 'directory' argument to point at the real build output (use an absolute path built from __file__ or Path(__file__).parent to avoid CWD issues).","If the file is named differently (e.g. 404.htm), rename it to exactly 'index.html' or '404.html', because only those two literals are checked/served as fallbacks.","If you intentionally want a lazy failure at request time instead of startup, pass fallback=None or 'auto' (auto resolves the file when serving) or set check_dir=False to skip eager validation."],"exampleFix":"# before\napp = FastAPI()\napp.mount(\"/\", _FrontendRoute(directory=\"frontend/dist\", fallback=\"index.html\", check_dir=True))  # dist not built yet\n\n# after\nimport subprocess\nsubprocess.run([\"npm\", \"run\", \"build\"], cwd=\"frontend\", check=True)  # ensure dist/index.html exists\napp.mount(\"/\", _FrontendRoute(directory=str(Path(__file__).parent / \"frontend\" / \"dist\"), fallback=\"index.html\", check_dir=True))","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef validate_frontend_dir(directory: str | Path, fallback: str | None) -> None:\n    d = Path(directory).resolve()\n    assert d.is_dir(), f\"directory missing: {d}\"\n    if fallback in {\"index.html\", \"404.html\"}:\n        f = d / fallback\n        assert f.is_file(), f\"fallback file missing: {f}\"","typeGuard":"def is_valid_fallback(value: str | None) -> bool:\n    return value in {\"auto\", \"index.html\", \"404.html\", None}","tryCatchPattern":"try:\n    route = _FrontendRoute(\"/\", directory=dist, fallback=\"index.html\", check_dir=True)\nexcept RuntimeError as e:\n    raise SystemExit(f\"Frontend build missing, run 'npm run build' first: {e}\") from e","preventionTips":["Build the frontend in CI before starting the API, and fail the pipeline if dist/index.html is absent.","Use absolute paths derived from __file__ for the static directory instead of CWD-relative strings.","Keep check_dir=True in production so a broken frontend deploy fails at startup, not per-request."],"tags":["fastapi","static-files","spa","startup","configuration"],"backgroundTag":null,"analyzedSha":"a1fa70d4237d50aae6586a0d9b229df583463d21","analyzedAt":"2026-08-14T19:41:58.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}