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:
- Frontend HTML:
<link rel="stylesheet" href="/static/css/style.css"> - JavaScript API calls:
fetch('/api/addGmailInbox') - 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/addGmailInboxx - 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
- Never hardcode absolute paths when your app might be deployed under a subpath
- Environment detection can be done simply by checking the hostname
- Dynamic asset loading with JavaScript is sometimes necessary for complex deployment scenarios
- Backend redirects need the same dynamic path treatment as frontend code
- 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):
- Environment variables: Set a
BASE_PATHenvironment variable instead of hostname detection - FastAPI root_path: Use FastAPI’s built-in
root_pathconfiguration - Reverse proxy configuration: Handle path rewriting at the proxy level
- 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.