Serving static assets on a subpath in Phoenix
If you create a new Phoenix project, without using the --no-html
flag, a static plug will be added to your endpoint. Because of this, a lot of people recommend to just edit that, if you want to serve static files from a subdirectory. However, this can get a bit tricky if you have data stored in different directories - or use Phoenix purely as an API.
When someone asked about this on the elixir-lang Slack, my first response was:
You don’t have to edit the endpoint, though, you can just use Plug.Static in a (sub)scope in your controller
I remembered I did get this working once but wasn’t quite sure how anymore, so I wanted to quickly test if my answer was correct…. and there was some caveats to it. I haven’t found a comprehensive guide on how to do this, so here’s what I learned.
This article assumes you already have a Phoenix project up and running, however, if you don’t, you can create one with mix phx.new --no-ecto --no-webpack phxstatic
- leaving out the database and frontend JS components for the sake of simplicity. For testing, we add a file at priv/test/hello.txt
that contains hello world
.
Adjusting our router
For serving static assets, we need to add a pipeline with the Plug.Static
plug to our router. We will also use that pipeline in the scope where we want to serve those files, using the pipe_through
macro.
|
|
Caveat 1: Even though the plug is nested in the /static/ scope, the :at option has to be set to the full path!
Caveat 2: It doesn’t do anything yet, now that’s unfortunate.
Making it work
Caveat 3: Now this is where I got stuck the first time. Everything looks right but why does it just display the phoenix 404 page? The reason for this is, that a pipeline is only invoked, once a route in the scope that uses it matches, as explained by José in this post.
So the solution is reasonably simple, we just add a catchall route to the respective scope. I am not including the source code of ErrorController.notfound
here, since you can use any controller/function here for rendering a 404.
|
|
If we now open localhost:4000/static/hello.txt
, we get hello world - yay! And if we try some other path in that scope, we will just get the response rendered by ErrorController.notfound
.