I’m writing an SPA in Phoenix with a GraphQL endpoint at /api. I’d like the following characteristics:
- No Phoenix views or templating are being used. All HTML is served from priv/static.
- I want to handle this entirely in Phoenix/Elixir to simplify deployment. No need for a linked web server for static files.
- The router only handles the /api path, or the /emails path for previewing emails in development mode.
- If a path isn’t matched from priv/static, priv/static/index.html should be served. This will render a JavaScript router which will either show the correct page, or render a 404 error.
- For bonus points, it’d be nice if /login detected the presence of /login/index.html and served that.
I have this so far.
plug Plug.Static,
at: "/", from: :app, gzip: false
plug AppWeb.Router
plug :not_found
def not_found(conn, _) do
send_file(conn, 200, "priv/static/index.html")
end
It works if I access http://localhost:4000/index.html, but if I access / the router kicks in and reports that there’s no route. Maybe this would work if I placed the router after not_found
, but then I don’t know how I’d distinguish a router miss from a missing static file.
Additionally, if I access /api, the API returns correctly but I get:
** (Plug.Conn.AlreadySentError) the response was already sent
This goes away if I comment out the Plug.Static setup, but that of course undoes all of my work.
How can I achieve this? Is there a better way? I don’t mind running everything through the router if I can tell it to serve up static files or modify the not_found behavior.