Fixing Static Asset and API Path Issues in FastAPI for Production Subpath Deployments

The Problem

I recently deployed a FastAPI Gmail OAuth application to a cloud platform under a subpath (/my-app/), and ran into a classic web development issue: static assets and API calls that worked perfectly on localhost were returning 404 errors in production.

The Setup:

  • Localhost: http://localhost:8080/
  • Production: https://api.example.com/my-app/

The Errors:

GET https://api.example.com/static/css/style.css 404 (Not Found)
GET https://api.example.com/static/js/main.js 404 (Not Found)
GET https://api.example.com/api/addGmailInbox 404 (Not Found)

The issue was that hardcoded paths like style.css work fine when your app runs at the root, but fail when deployed under a subpath where they should be /my-app/static/css/style.css.

The Root Cause

The application had several hardcoded absolute paths:

  1. Frontend HTML: <link rel="stylesheet" href="/static/css/style.css">
  2. JavaScript API calls: fetch('/api/addGmailInbox')
  3. Backend redirects: return RedirectResponse('/static/html/success.html')

These paths assumed the app would always run at the domain root, which isn’t true for subpath deployments.

The Solution

1. Dynamic Path Detection in Frontend

Instead of hardcoded CSS/JS links, I implemented JavaScript-based dynamic loading:

<!-- OLD: Hardcoded paths -->
<link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/main.js"></script>

<!-- NEW: Dynamic paths -->
<script>
    // Dynamic path detection for production subpath support
    const isProduction = window.location.hostname.includes('api.example.com');
    const basePath = isProduction ? '/my-app' : '';

    // Add CSS with correct path
    const css = document.createElement('link');
    css.rel = 'stylesheet';
    css.href = basePath + '/static/css/style.css';
    document.head.appendChild(css);

    // Store base path for use in other scripts
    window.APP_BASE_PATH = basePath;
</script>

2. Dynamic API Calls in JavaScript

Updated the JavaScript to use the dynamic base path for API calls:

// OLD: Hardcoded API path
const response = await fetch('/api/addGmailInbox', {
    method: 'POST',
    // ...
});

// NEW: Dynamic API path
const basePath = window.APP_BASE_PATH || '';
const apiUrl = basePath + '/api/addGmailInbox';
const response = await fetch(apiUrl, {
    method: 'POST',
    // ...
});

3. Dynamic Redirects in Backend

Added a helper function to detect the environment and return the correct base path:

def get_base_path(request: Request) -> str:
    """Get the base path for the current environment"""
    # Check if we're in production by looking at the host
    host = request.headers.get("host", "")
    if "api.example.com" in host:
        return "/my-app"
    return ""

# OLD: Hardcoded redirect
return RedirectResponse(f"/static/html/success.html?{urlencode(params)}")

# NEW: Dynamic redirect
base_path = get_base_path(request)
return RedirectResponse(f"{base_path}/static/html/success.html?{urlencode(params)}")

The Results

After implementing these changes:

Localhost (works as before):

  • Static assets: http://localhost:8080/static/css/style.css
  • API calls: http://localhost:8080/api/addGmailInbox x
  • OAuth redirects: http://localhost:8080/static/html/success.html

Production (now works):

  • Static assets: https://api.example.com/my-app/static/css/style.css
  • API calls: https://api.example.com/my-app/api/addGmailInbox
  • OAuth redirects: https://api.example.com/my-app/static/html/success.html

Key Lessons Learned

  1. Never hardcode absolute paths when your app might be deployed under a subpath
  2. Environment detection can be done simply by checking the hostname
  3. Dynamic asset loading with JavaScript is sometimes necessary for complex deployment scenarios
  4. Backend redirects need the same dynamic path treatment as frontend code
  5. Test in production-like environments early to catch these issues

Alternative Approaches

While my solution works, there are other approaches worth considering (Im still new to FastAPI):

  1. Environment variables: Set a BASE_PATH environment variable instead of hostname detection
  2. FastAPI root_path: Use FastAPI’s built-in root_path configuration
  3. Reverse proxy configuration: Handle path rewriting at the proxy level
  4. Relative paths: Use relative paths where possible (though this has limitations)

The JavaScript-based approach I used is straightforward and doesn’t require complex server configuration, making it a good choice for platform-as-a-service deployments where you have limited control over the infrastructure.

Conclusion

Subpath deployments are common in microservices architectures and shared hosting environments. Having a strategy to handle dynamic paths from the start can save hours of debugging later. The key is to avoid hardcoded absolute paths and implement environment-aware path construction throughout your application stack.

Continue Reading