In Part 1, we explored the request flow between Nginx, Vue (frontend), and Rails (API backend). We also covered how Nginx routes traffic and why caching matters in such a setup.
Now in Part 2, we’ll go deeper into asset caching strategies โ specifically tailored for a Rails API-only backend + Vue frontend deployed with Nginx.
๐ The Core Idea
- HTML files (like
vite.html) should never be cached. They are the entry point of the SPA and change frequently. - Hashed assets (like
/vite/index-G34XebCm.js) can be cached for 1 year safely, because the hash ensures cache-busting. - Non-hashed assets (images, fonts, legacy JS/CSS) should get short-term caching (e.g., 1 hour).
This split ensures fast repeat visits while avoiding stale deploys.
๐ Example: Files in public/vite/
Your build pipeline (via Vite) outputs hashed assets like:
vite/
index-G34XebCm.js
DuckType-CommonsRegular-CSozX1Vl.otf
Allergens-D48ns5vN.css
LoginModal-DR9oLFAS.js
Notice the random-looking suffixes (G34XebCm, D48ns5vN) โ these are hashes. They change whenever the file content changes.
โก๏ธ That’s why they’re safe to cache for 1 year: a new deploy creates new filenames, so the browser will fetch fresh assets.
By contrast, files like:
assets/
15_minutes.png
Sky_background.png
do not have hashes. If you update them, the filename doesn’t change, so the browser might keep showing stale content if cached too long. These need shorter cache lifetimes.
๐ ๏ธ Final Nginx Caching Configuration
Here’s the Nginx cache snippet tuned for your setup:
# =====================
# HTML (always no-cache)
# =====================
location = /vite.html {
add_header Cache-Control "no-cache";
}
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# ==============================
# Hashed Vue/Vite assets (1 year)
# ==============================
location ^~ /vite/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# ==================================================
# Other static assets (non-hashed) - 1 hour caching
# ==================================================
location ~* \.(?:js|css|woff2?|ttf|otf|eot|jpg|jpeg|png|gif|svg|ico)$ {
add_header Cache-Control "public, max-age=3600";
}
๐ Explanation
location = /vite.htmlโ explicitly disables caching for the SPA entry file.location ~* \.html$โ covers other.htmlfiles just in case.location ^~ /vite/โ everything inside/vite/(all hashed JS/CSS/images/fonts) gets 1 year caching.- Final block โ fallback for other static assets like
/assets/*.png, with only 1-hour cache.
โ ๏ธ What Happens If We Misconfigure?
- If you cache
.htmlโ new deploys wonโt show up, users may stay stuck on the old app shell. - If you cache non-hashed images for 1 year โ product images may stay stale even after updates.
- If you donโt use
immutableon hashed assets โ browsers may still revalidate unnecessarily.
๐๏ธ Real-World Examples
- GitLab uses a similar strategy with hashed Webpack assets, caching them long-term via Nginx and Cloudflare.
- Discourse does long-term caching of fingerprinted JS/CSS, but keeps HTML dynamic with no-cache.
- Basecamp (Rails + Hotwire) fingerprints all assets, leveraging 1-year immutable caching.
These projects rely heavily on content hashing + Nginx headers โ exactly what we’re setting up here.
โ Best Practices Recap
- Always fingerprint (hash) assets in production builds.
- Cache HTML for 0 seconds, JS/CSS hashed files for 1 year.
- Use
immutablefor hashed assets. - Keep non-hashed assets on short lifetimes or rename them when updated.
This ensures smooth deploys, lightning-fast repeat visits, and no stale content issues.
๐ In Part 3, we’ll go deeper into Rails + Passenger integration, showing how Rails API responses fit into this caching strategy (and what not to cache at the API layer).