tiangolo/fastapi · error · AssertionError

A frontend path must start with '/'

Error message

A frontend path must start with '/'

What it means

Raised by `_normalize_frontend_path` (fastapi/routing.py:1857) when `path` is non-empty but does not start with '/'. FastAPI route paths (including frontend mount paths) are URL paths and must be absolute, so a relative-looking value like 'app' or 'frontend' is rejected at setup time with an AssertionError.

Solutions

  1. Prefix the value with '/': `router.frontend('/' + value.lstrip('/'), directory='dist')`.
  2. Use the conventional '/' to mount at the app root: `router.frontend('/', directory='dist')`.
  3. Validate the value at startup: `assert path.startswith('/')` before calling `frontend`.

Example fix

// before
router.frontend('app', directory='dist')
// after
router.frontend('/app', directory='dist')
Defensive patterns

Strategy: validation

Validate before calling

def normalize_frontend_path(path: str) -> str:
    if not path.startswith('/'):
        path = '/' + path.lstrip('/')
    return path

router.frontend(normalize_frontend_path(raw), directory='dist')

Type guard

def is_absolute_frontend_path(path: object) -> bool:
    return isinstance(path, str) and path.startswith('/')

Prevention

When it happens

Trigger: Calling `router.frontend('app', directory='dist')`, `router.frontend('frontend/', directory='dist')`, or any value whose first character is not '/'. Building the path by concatenation that drops the leading slash (e.g. `frontend(env_base.lstrip('/'), ...)`).

Common situations: Reusing a filesystem directory name as the URL path without a leading slash. Reading a path from config/env that was stored without '/'. Constructing a prefix dynamically and forgetting to prepend '/'.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/a554f22b66fa841c. Report an issue: GitHub.

Appendix: source

Thrown at fastapi/routing.py:1857

                yield RouteContext(original_route, route_context)


def _iter_routes_with_context(
    routes: Sequence[BaseRoute],
) -> Iterator[tuple[BaseRoute, _EffectiveRouteContext | None]]:
    for route in routes:
        if isinstance(route, _IncludedRouter):
            for route_context in route.effective_route_contexts():
                yield route_context.original_route, route_context
        else:
            yield route, None


def _normalize_frontend_path(path: str) -> str:
    if not path:
        raise AssertionError("A frontend path cannot be empty")
    if not path.startswith("/"):
        raise AssertionError("A frontend path must start with '/'")
    if path != "/":
        path = path.rstrip("/")
    return path


def _join_frontend_paths(prefix: str, path: str) -> str:
    if not prefix:
        return path
    if path == "/":
        return prefix
    return prefix + path


def _frontend_path_specificity(path: str) -> int:
    if path == "/":
        return 0
    return len(path)

View on GitHub (pinned to 3e8d1526d8)