2025, Sep 24 07:00

Stable FastAPI routes on Vercel: handle trailing slashes without redirects by mapping empty-path and slash routes

FastAPI endpoints fail on Vercel due to trailing slash redirects. Disable RedirectSlashes and expose both empty and '/' routes to work reliably with Next.js.

FastAPI routes work one way locally and a different way on Vercel? If your endpoints intermittently fail depending on a trailing slash, you are likely hitting a redirect normalization mismatch. Locally, FastAPI is forgiving with slashes; on Vercel, the edge/proxy stack treats them differently and may cause loops, 404s, or even 500s. Here is a practical way to make your API stable across both environments.

Minimal example that reproduces the issue

The route below lives under a router with a /users prefix. Locally it behaves well with or without the trailing slash, thanks to FastAPI’s default redirect behavior.

from fastapi import APIRouter, Depends
from ..schemas.user import UserView
from ..auth import ensure_admin
from ..repositories.users import fetch_user_list
users_api = APIRouter(prefix="/users", tags=["users"]) 
@users_api.get("/", response_model=list[UserView])
def fetch_users(_: UserView = Depends(ensure_admin)):
    return fetch_user_list()

In local development with Uvicorn, a request to /api/users redirects to /api/users/ (307) and the route matches. On Vercel, however, calling /api/users/ may produce 500, and /api/users may result in a 404 or net::ERR_TOO_MANY_REDIRECTS. Switching the decorator to an empty path, like @get(""), flips the situation: Vercel starts working, but local requests begin returning 404.

What’s actually happening

This is a trailing-slash normalization mismatch. FastAPI (via Starlette) enables RedirectSlashes by default. Locally, that means /api/users is automatically redirected to /api/users/, which then matches the @get("/") route. On Vercel, something in the edge/proxy layer normalizes or strips the trailing slash before it reaches your app, and the automatic redirect loop either gets stripped, loops, or is not handled the same way. The outcome is that only a route defined at "" is reliable there, while local development expects the redirect to land on "/".

Proxies are in play: in development, Next.js forwards /api/:path* to FastAPI, and in production Vercel routes /api/(.*) → /api/index.py. These layers influence how the slash is preserved or redirected before your FastAPI app handles the request.

The fix that works across environments

The stable approach is to stop relying on RedirectSlashes and explicitly serve both versions of the path. First, disable the automatic redirect in your FastAPI application. Then, expose two handlers for the same function: one for the empty path and one for the trailing slash. This way both /api/users and /api/users/ resolve straight to your code, no redirects required.

# main/index.py
from fastapi import FastAPI
core_app = FastAPI(title=config.APP_TITLE, lifespan=app_lifespan, redirect_slashes=False)
# routes/users.py
from fastapi import APIRouter, Depends
from ..schemas.user import UserView
from ..auth import ensure_admin
from ..repositories.users import fetch_user_list
users_api = APIRouter(prefix="/users", tags=["users"]) 
@users_api.get("", include_in_schema=False, response_model=list[UserView])  # /api/users
@users_api.get("/", response_model=list[UserView])                          # /api/users/
def fetch_users(_: UserView = Depends(ensure_admin)):
    return fetch_user_list()

This dual-decorator pattern removes ambiguity and avoids any slash-based redirects that might be treated differently by Vercel. The include_in_schema=False flag keeps your OpenAPI UI clean by documenting a single canonical path while still accepting requests on both.

Why it matters

APIs behind proxies and CDNs often encounter subtle differences in path normalization. Depending on redirects at the framework layer can be brittle when an edge network simplifies or rewrites the request path first. Eliminating the redirect and explicitly handling both forms of the URL ensures your endpoints remain reachable in local development, in serverless environments, and through multi-hop proxies. It also prevents frustrating failures like 404s and redirect loops that are hard to reproduce.

Conclusion

If an endpoint works locally but fails on Vercel depending on a trailing slash, disable RedirectSlashes and map both the slash and no-slash variants to the same handler. Keep one documented in the schema and hide the other. This small change avoids redirect ambiguity across environments and stabilizes your FastAPI + Next.js deployment on Vercel without toggling code per environment.

The article is based on a question from StackOverflow by AmericaDoodles and an answer by AmericaDoodles.