Using NGINX on Heroku to Serve Single Page Apps and Avoid CORS
I published an article recently Killing CORS Preflight Requests on a React SPA which demonstrated some ways to prevent browsers from sending CORs preflight requests that slows down your app with almost no benefit.
That article was part 1 of a two part exploration of removing preflight requests, which explained how to make requests simple (no infrastructure involved) and this follow up details using a NGINX reverse proxy to route API calls through the same domain and avoid CORS altogether.
Why Proxy at all?
My initial desire in usingsimple requests was to keep things simple (see what I did) and avoid adding infrastructure. As our app evolved, more of the stack on Relay-GraphQL that sends every request using POST. Mentioned previously, the only way to make a POST request simple is to cheat on Content-Type by setting it to “text/plain” even though the content is actually JSON.
Even if we’re willing to do the hack, it seems like a losing battle where there’s only one way to keep a request classified as simple and a myriad of ways to end up with a preflight request. Also, putting the token in the URL is inherently “leakier” as they show up in logs and error reporting services ( /endpoint?token=secret) .
- Simpler to work with, feels like a server to server request
- Works perfectly with any third party API calls you make from the client, regardless of whether they support CORS
Overall, I believe proxying is the solution that will go the distance after trying both.
The search for a reverse proxy solution led me to find an awesome static buildpack for Heroku. This post ended up to be mostly about killing the proverbial two birds, using it to serve your single page app and also getting rid of CORS.
If you’re serving a single page app on Heroku and have CORS issues, I hope this is a useful guide. If you’re hosted somewhere else, skip to the NGINX config portion to see how simple it is to avoid CORS.
Learned from Hackernews feedback on my other post to express the scope of the article earlier so not to waste readers’ time, thank you for that.
Getting NGINX on your Heroku App
First, we have to get NGINX installed on your dynos as part of the build process. If you’re using Heroku to serve a static app, there’s usually a lot of memory to spare given the limit of 512mb.
We should strive for a setup where the proxy configuration is expressed in the project’s repository (e.g. a proxy.json file) to have full transparency into how the proxy works and to make maintenance easier for future developers.
The initial idea was to shop for a reliable custom buildpack that installs NGINX during the deployment and sets up proxy_pass via a config file in the app. However, a team mate (thanks Tor !) pointed me to Heroku’s static buildpack during a water cooler chat and it elegantly solves both the CORS problem and serving our single page app.
Advantages over custom NGINX buildpack:
- A nice high level config that avoids having to directly mess with NGINX
- Serves your single page app’s index page with support for clean urls and HTML5 routing
- Serves your static assets (JS, CSS, images) and you can add headers to each of them for caching and cross origin headers for CDNs
- Gzip and Heroku logging setup by default
A major caveat is that it’s clearly stated to be an “experimental OSS project”, but we tested it in production for about a month now with no issues.
Thanks to this nifty buildpack, we could remove Express from our setup and our memory usage actually went down
Here’s a sample of our static.json to showcase some of the offered features.
Let’s start with sending assets to the root folder
This is where you expose the publicly viewable files of your app, commonly named dist on many projects.
There are two typical ways you can preprocess and build a static bundle on Heroku. You can build locally (e.g. using Webpack / gulp) and check in your dist folder or do it on push, using npm’s postinstall call. I much prefer the latter since it allows the team to maintain one machine config when building our production bundles. You can read more about the post install recommendations on this Heroku guide .
Our project’s postinstall:
“postinstall”: “./node_modules/webpack/bin/webpack.js --config ./webpack.config.js”
The above command builds our productions and dumps all of them in the dist folder, including:
- index.html (our static app)
- pictures and webfonts
Routes for HTML5 PushState
If you’re using clean HTML5 routing, this allows you to redirect all routes to your root page and the client side router can take over.
The buildpack does not cause a redirect if you have a file matching the route, hence /** does not match /styles.css making it super easy to setup.
Setting up the CORS proxy
Finally, we’re getting to this. If you namespaced your API to something like /api , this is really easy. In the proxies section, each key defines a Location rule on NGINX.
From our example config, by redirecting “/api/”, visiting /api/v1/candidates.json redirects to backend.example.com/api/v1/candidates.json .
That’s it, CORS problem solved. My apologies if that was underwhelming to you 😛
Bonus benefit of using the buildpack — You can instruct it to return cache headers for CSS and JS assets. Please only do so if you fingerprinted them for cache busting.
If you use a CDN, there’s a high chance you need to whitelist your distribution as an allowed origin because it will hit your app for the asset if it’s not cached yet. For example, AWS’s Cloudfront has a custom origin mode that does just that.
HTTPS only please
I recommend setting https_only to true, just a friendly reminder
Putting everything together
Once you have the configuration up, I suggest forking your production app and testing the deployment process before moving forward. It’s quite easy to get a blank screen when messing with the buildpack configuration.
Find out the current buildpack configuration:
-> heroku buildpacks --app frontend-app
=== frontend-app Buildpack URLs
Add the static buildpack to the LAST index
-> heroku buildpacks:add --index 2 https://github.com/heroku/heroku-buildpack-static.git --app frontend-app
Buildpack set. Next release on frontend-app will use:
Now, you’re ready to delete away the Procfile and let the buildpack use NGINX to serve the app. If you did it correctly, the Heroku dashboard should show something like this:
Woah magic… That’s it?
Wait, this seems way too simple. No Procfile, how does that work?
In essence, all the buildpack does is assume that you’re building static assets and storing them into a dist folder and telling NGINXto serve this folder, kind of like Rails’ public folder or the /var/www folder. It comes with a few bonus web best practice features like enforcing https, headers and gzipping by default which I think that makes it a complete solution.
We have to add the static buildpack last so it boots NGINX to serve requests just like the diagram above. This means that your standard buildchain (be it Ruby or NodeJS) can work as normal, compiling and sending the static assets into the root folder.
Digging into the Config and Proxy Pass
Remember that proxies section in static.json? Each item eventually translates to a proxy_pass directive and as of publication, this is exactly how much code there is on the config.
I like the simplicity but this might not be enough for you. For example, it does not forward headers so it’s not easy to track the performance of requests. Fear not, there are people using the buildpack and actively sending pull requests.
Send typical proxy headers by ankon · Pull Request #33 · heroku/heroku-buildpack-static
It would be nice if the proxy logic would send over some headers to the origin server: 1. HTTP/1.1 usage 2. X-Forwarded… github.com
Thank you for reading this! I hope this illustrated the benefits of using avoiding CORS with an NGINX reverse proxy and the benefits of using the static buildpack.
After putting this into production, the team managed to get rid of Express and a bunch of middleware to achieve the same functionality. I also trust NGINX to be equally performant if not much more.
If you’re trying any of the recommendations in this guide, post a response and let me know how it went.
If you enjoyed the article and have a keen interest in working on modern stacks utilizing Rails, React and Ember, we are hiring .
转载本站任何文章请注明：转载至神刀安全网，谢谢神刀安全网 » Using NGINX on Heroku to Serve Single Page Apps and Avoid CORS