Deploy React's build folder via FastAPI

Question:

I want to serve my React frontend using FastAPI. The goal being 0 Javascript dependency for the user. The user can simply download the Python code, start server, and view the website on localhost.

My folder structure is:

- my-fullstack-app
  - frontend/
    - build/
    - public/
    - ...
    - package.json
  - backend/
    - main.py
    - static/

I ran npm run build to generate the frontend/build/ folder which contains:

build/
├── asset-manifest.json
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── static
    ├── css
    │   ├── main.073c9b0a.css
    │   └── main.073c9b0a.css.map
    ├── js
    │   ├── 787.cda612ba.chunk.js
    │   ├── 787.cda612ba.chunk.js.map
    │   ├── main.af955102.js
    │   ├── main.af955102.js.LICENSE.txt
    │   └── main.af955102.js.map
    └── media
        └── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg

I copied the contents of the frontend/build/ folder inside backend/static/.

Now, I want to serve this backend/static/ folder via FastAPI as opposed to running another server.

In my FastAPI’s main.py I have:

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

app.mount("/", StaticFiles(directory="static/"), name="static")

I then start the server using – uvicorn main:app --reload.

But it doesn’t work.

When I open http://127.0.0.1:8000/ in the browser, the output is a JSON file which says {"detail":"Not Found"} and console has Content Security Policy: The page's settings blocked the loading of a resource at http://127.0.0.1:8000/favicon.ico ("default-src")..

How do I get this to work? I’ve seen examples for similar functionality with React and Express.

Asked By: theairbend3r

||

Answers:

The answer mentioned by @flakes is exactly what I was looking for. Check it out for more details.

For completeness,

Without html=True flag.

app.mount("/", StaticFiles(directory="static/"), name="static")

Navigate to http://127.0.0.1:8000/index.html to view the html file.


With html=True flag.

app.mount("/", StaticFiles(directory="static/", html=True), name="static")

Navigate to http://127.0.0.1:8000/ to view the html file.

Answered By: theairbend3r

What you can do is have the index.html serve all routes except API specific ones, then load all static assets. My approach looks like this:

from fastapi import FastAPI, Request
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.exception_handlers import http_exception_handler
from starlette.exceptions import HTTPException as StarletteHTTPException

def SPA(app: FastAPI, build_dir: Union[Path, str]) -> FastAPI:
# Serves a React application in the root directory

    @app.exception_handler(StarletteHTTPException)
    async def _spa_server(req: Request, exc: StarletteHTTPException):
        if exc.status_code == 404:
            return FileResponse(f'{build_dir}/index.html', media_type='text/html')
        else:
            return await http_exception_handler(req, exc)

    if isinstance(build_dir, str):
        build_dir = Path(build_dir)

    app.mount(
        '/static/',
        StaticFiles(directory=build_dir / 'static'),
        name='React app static files',
    )

Then in your entry point file

app: FastAPI = SPA(FastAPI(title='PROJECT_NAME', './build')
Answered By: Mark
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.