HTTP Caching 101: Things You Need to Know
Grace Collins
Solutions Engineer · Leapcell
What is HTTP Caching
HTTP caching is a technology that improves web performance by reducing server load, speeding up client response times, and saving network bandwidth. There are two types of HTTP caching: forced caching and negotiated caching.
Forced Caching
Forced caching allows the client to use locally cached resources directly within a specified time period without sending a request to the server. Forced caching is controlled by response headers specified by the server, primarily through two fields: Cache-Control and Expires.
Cache-Control
Cache-Control is a general-purpose header that specifies the maximum validity period (max-age
), whether the cache can be shared (public
or private
), and whether modifications are allowed (no-cache
or no-store
).
Example:
Cache-Control: max-age=3600
The above indicates that the resource is valid for 3600 seconds and can be cached.
Expires
Expires is a deprecated field that specifies the absolute expiration time for the cache.
Example:
Expires: Wed, 23 Aug 2024 03:36:26 GMT
This means the resource will expire at 3:36:26 AM on August 23, 2024.
If both Cache-Control and Expires are present, Cache-Control takes precedence.
Negotiated Caching
Negotiated caching requires the client to check with the server if the resource has been updated for every request. If not updated, the server responds with a 304
status code and an empty response body, allowing the client to continue using the local cache. If updated, the server returns a 200
status code and the new resource, replacing the local cache. Negotiated caching involves headers from both the server and client, primarily Last-Modified/If-Modified-Since and ETag/If-None-Match.
Last-Modified/If-Modified-Since
Last-Modified is a server-side field indicating the last modification time of the resource. Example:
Last-Modified: Tue, 22 Aug 2024 02:36:26 GMT
This means the resource was last modified on August 22, 2024, at 2:36:26 AM.
If-Modified-Since is a client-side field indicating the last retrieval time of the resource. Example:
If-Modified-Since: Tue, 22 Aug 2024 02:36:26 GMT
This means the client retrieved the resource on August 22, 2024, at 2:36:26 AM.
If the two timestamps are equal or Last-Modified is earlier, the resource has not been updated. If Last-Modified is later, the resource has been updated.
ETag/If-None-Match
ETag is a server-side field representing the unique identifier of a resource. Example:
ETag: '5d3a9f6d-1f86'
This indicates the resource's identifier is "5d3a9f6d-1f86"
.
If-None-Match is a client-side field indicating the expected identifier of a resource. Example:
If-None-Match: '5d3a9f6d-1f86'
This indicates the client expects the resource identifier to be "5d3a9f6d-1f86"
.
If the two values match, the resource has not been updated. If they differ, the resource has been updated.
HTTP Caching Best Practices
Combining negotiated caching and forced caching can effectively reduce unnecessary network requests while ensuring users always have the latest content.
General Approach:
Forced Caching: For static resources (e.g., CSS, JS, images), set a long cache duration. This allows browsers to directly retrieve resources from local storage without contacting the server.
Negotiated Caching: For resources that may change, use negotiated caching. Browsers will send requests to check if the resource has changed. If not, the server responds with a 304 Not Modified
response, allowing the browser to use the local cache. If the resource has changed, the server responds with a 200 OK
and the updated resource.
Example Implementation:
Suppose we are using Express.js as the backend framework:
const express = require('express'); const app = express(); // Forced caching: Set cache for static resources app.use( '/static', express.static('public', { maxAge: '1y', // Cache duration is one year }) ); // Negotiated caching: Using ETag and Last-Modified app.get('/resource', (req, res) => { const content = '...'; // Fetch the resource content const etag = generateETag(content); // Generate ETag // Check the If-None-Match header if (req.headers['if-none-match'] === etag) { res.status(304).end(); // Resource unchanged, return 304 } else { res.setHeader('ETag', etag); res.send(content); // Resource changed, return new content } }); function generateETag(content) { return require('crypto').createHash('md5').update(content).digest('hex'); } app.listen(3000, () => { console.log('Server is running on port 3000'); });
Key Considerations
-
Version Control: To maximize forced caching effectiveness, include version information in resource URLs, e.g.,
/static/js/main.2024082301.js
. When resources are updated, change the version number to ensure users always get the latest version. -
Cost of Negotiated Caching: While negotiated caching reduces unnecessary data transfer, it still requires a network round trip. For resources that rarely change, forced caching might be more efficient.
We are Leapcell, your top choice for hosting backend projects
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ