Serving SPAs from Starlette

I’m using Starlette for a pet project because it’s a modern Python framework that has GraphQL support. I had trouble integrating it with a Single Page App, but I found an easy fix.

My client-side app gets built in the build/ directory, so just adding StaticFiles gets you off the ground:

route = [
    ...,
    Mount("", app=StaticFiles(directory="build", html=True)),
]

The History API makes it look like your URL is changing, but if you ever reload, you’ll get a 404. How can we get all URLs to route to the root index.html?

class SpaStaticFiles(StaticFiles):
    """
    Staticfiles for single-page-apps

    Wraps the base `lookup_path` to fallback to the root `index.html` so
    the JavaScript router takes over.
    https://github.com/encode/starlette/blob/d6d0f83d3fae766cee79aa444986e83c267a3a05/starlette/staticfiles.py#L145
    """

    async def lookup_path(self, path):
        full_path, stat_result = await super().lookup_path(path)
        if stat_result is None:
            return await super().lookup_path("./index.html")

        return full_path, stat_result

Now you just have to change the route to use our version of StaticFiles:

route = [
    ...,
    Mount("", app=SpaStaticFiles(directory="build", html=True)),
]

Appendix

Starlette’s static files docs: https://www.starlette.io/staticfiles/

Leave a Reply

Your email address will not be published. Required fields are marked *