pythonnext.jsfastapivercelserverless

FastAPI route works locally with `/` but not on Vercel (and vice versa)


I have a FastAPI + Next.js project deployed on Vercel. Locally everything works, but on Vercel some routes break depending on whether I use a trailing slash in the route definition.

For example, in my usrs.py router:

 from fastapi import APIRouter, Depends
 from ..schemas.user import UserOut
 from ..auth import require_admin
 from ..repositories.users import list_users

 user_router = APIRouter(prefix="/users", tags=["users"])

 @user_router.get("/", response_model=list[UserOut]) 
 def get_users(_: UserOut = Depends(require_admin)):
    return list_users()

Locally (Uvicorn)

GET http://localhost:8001/api/users/ works. GET http://localhost:8001/api/users redirects to /users/ as expected.

On Vercel (serverless FastAPI):

GET https://myapp.vercel.app/api/users/ → 500 GET https://myapp.vercel.app/api/users 404 or .vercel.app/api/users/ net::ERR_TOO_MANY_REDIRECTS

So I tried removing the slash in the route definition:

@user_router.get("", response_model=list[UserOut]) 
def get_users(_: UserOut = Depends(require_admin)):
    return list_users()

Now it works on Vercel, but fails locally (404).

What I’ve checked

DB and schema are fine (/api/db-check and /api/_schema_status return OK).

Other endpoints like /api/healthz and /api/users/login work fine.

My next.config.js proxies /api/:path* → FastAPI in dev, and Vercel vercel.json routes /api/(.*) → /api/index.py.

Question

  1. Why is there this incompatibility between local and Vercel regarding trailing slashes?
  2. How can I define my FastAPI routes so they work both locally and on Vercel without having to change code?

Solution

  • I resolved this issue. I've run into the "tailing-slash" mismatch between FastAPI/Starlette and the way Vercel normalize paths.

    The robust fix (works everywhere)

    1. Turn off auto redirecting and

    2. Serve both paths for every collection endpoint

    
       \# main/index.py
    
       from fastapi import FastAPI
    
       app = FastAPI(title=settings.APP_TITLE, lifespan=lifespan, redirect_slashes=False)
    
    # routes/users.py
    from fastapi import APIRouter, Depends
    from ..schemas.user import UserOut
    from ..auth import require_admin  
    
    user_router = APIRouter(prefix="/users", tags=["users"])
    
    @user_router.get("", include_in_schema=False, response_model=list[UserOut])  # /api/users
    @user_router.get("/", response_model=list[UserOut])                          # /api/users/
    def get_users(_: UserOut = Depends(require_admin)):
        return list_users()
    

    This dual-decorator pattern for the users collection resolved above issue. This removes the ambiguity and avoids relying on redirects that may be handled differently by Vercel.