HTTP
304 - Not Modified
Getting a 304 Not Modified means the resource hasn't changed since your last request—the ETag or Last-Modified timestamp matches, so you can use your cached version. This client-side informational response (3xx) saves bandwidth by not sending the full response. Most common when conditional requests use If-None-Match or If-Modified-Since, but also appears when cached resources are still valid, ETags match, or Last-Modified timestamps are unchanged.
#Common Causes
- →Frontend: Resource cached and still valid. ETag matches If-None-Match. Last-Modified unchanged. Conditional request sent.
- →Backend: Resource not modified since last request. ETag validation passed. Last-Modified check passed. Cache validation successful.
- →Infrastructure: CDN serves cached content. Reverse proxy cache valid. Load balancer cache hit.
✓Solutions
- 1Step 1: Diagnose - Check DevTools Network tab—304 responses mean cache is valid. Review ETag headers. Check Last-Modified headers. Verify cache is working.
- 2Step 2: Diagnose - Server logs show cache validation. Review ETag generation. Check Last-Modified timestamps. Verify conditional request handling.
- 3Step 3: Fix - Client-side: Use cached version when 304 received. Implement ETag/Last-Modified caching. Handle conditional requests properly. Update cache on 200.
- 4Step 4: Fix - Server-side: Return 304 with proper headers. Generate ETags correctly. Set Last-Modified timestamps. Implement conditional request handling.
- 5Step 5: Fix - Infrastructure: Configure CDN cache headers. Set reverse proxy cache. Update load balancer cache settings.
</>Code Examples
Fetch API: Handle 304 Not Modified
1// Client-side: Handle 304 and use cache
2async function fetchWithCache(url) {
3 const cached = localStorage.getItem(`cache-${url}`);
4 const cacheData = cached ? JSON.parse(cached) : null;
5
6 // Build conditional request headers
7 const headers = {};
8 if (cacheData?.etag) {
9 headers['If-None-Match'] = cacheData.etag;
10 }
11 if (cacheData?.lastModified) {
12 headers['If-Modified-Since'] = cacheData.lastModified;
13 }
14
15 const response = await fetch(url, { headers });
16
17 if (response.status === 304) {
18 // Resource not modified - use cached version
19 console.log('Using cached version (304 Not Modified)');
20 return cacheData.data;
21 }
22
23 // Resource modified - update cache
24 const data = await response.json();
25 const etag = response.headers.get('ETag');
26 const lastModified = response.headers.get('Last-Modified');
27
28 // Update cache
29 localStorage.setItem(`cache-${url}`, JSON.stringify({
30 data,
31 etag,
32 lastModified,
33 timestamp: Date.now(),
34 }));
35
36 return data;
37}
38
39// ETag-based caching
40async function fetchWithETag(url) {
41 const cached = localStorage.getItem(`etag-${url}`);
42 const cacheData = cached ? JSON.parse(cached) : null;
43
44 const headers = {};
45 if (cacheData?.etag) {
46 headers['If-None-Match'] = cacheData.etag;
47 }
48
49 const response = await fetch(url, { headers });
50
51 if (response.status === 304) {
52 return cacheData.data;
53 }
54
55 const data = await response.json();
56 const etag = response.headers.get('ETag');
57
58 localStorage.setItem(`etag-${url}`, JSON.stringify({
59 data,
60 etag,
61 timestamp: Date.now(),
62 }));
63
64 return data;
65}
66
67// Last-Modified caching
68async function fetchWithLastModified(url) {
69 const cached = localStorage.getItem(`modified-${url}`);
70 const cacheData = cached ? JSON.parse(cached) : null;
71
72 const headers = {};
73 if (cacheData?.lastModified) {
74 headers['If-Modified-Since'] = cacheData.lastModified;
75 }
76
77 const response = await fetch(url, { headers });
78
79 if (response.status === 304) {
80 return cacheData.data;
81 }
82
83 const data = await response.json();
84 const lastModified = response.headers.get('Last-Modified');
85
86 localStorage.setItem(`modified-${url}`, JSON.stringify({
87 data,
88 lastModified,
89 timestamp: Date.now(),
90 }));
91
92 return data;
93}Express.js: Return 304 for Unchanged Resources
1// Server-side: Return 304 for conditional requests
2const express = require('express');
3const app = express();
4
5// ETag-based conditional request
6app.get('/api/resource/:id', async (req, res) => {
7 const resource = await db.resources.findById(req.params.id);
8
9 // Generate ETag from resource
10 const etag = generateETag(resource);
11 res.set('ETag', etag);
12
13 // Check If-None-Match header
14 const ifNoneMatch = req.headers['if-none-match'];
15 if (ifNoneMatch === etag) {
16 // Resource not modified
17 return res.status(304).end();
18 }
19
20 // Resource modified - return full response
21 res.json(resource);
22});
23
24// Last-Modified conditional request
25app.get('/api/data/:id', async (req, res) => {
26 const data = await db.data.findById(req.params.id);
27 const lastModified = new Date(data.updatedAt).toUTCString();
28
29 res.set('Last-Modified', lastModified);
30
31 // Check If-Modified-Since header
32 const ifModifiedSince = req.headers['if-modified-since'];
33 if (ifModifiedSince) {
34 const modifiedSince = new Date(ifModifiedSince);
35 const resourceModified = new Date(data.updatedAt);
36
37 if (resourceModified <= modifiedSince) {
38 // Resource not modified
39 return res.status(304).end();
40 }
41 }
42
43 // Resource modified - return full response
44 res.json(data);
45});
46
47// Combined ETag and Last-Modified
48app.get('/api/content/:id', async (req, res) => {
49 const content = await db.content.findById(req.params.id);
50 const etag = generateETag(content);
51 const lastModified = new Date(content.updatedAt).toUTCString();
52
53 res.set('ETag', etag);
54 res.set('Last-Modified', lastModified);
55
56 // Check both conditions
57 const ifNoneMatch = req.headers['if-none-match'];
58 const ifModifiedSince = req.headers['if-modified-since'];
59
60 if (ifNoneMatch === etag) {
61 return res.status(304).end();
62 }
63
64 if (ifModifiedSince) {
65 const modifiedSince = new Date(ifModifiedSince);
66 const resourceModified = new Date(content.updatedAt);
67 if (resourceModified <= modifiedSince) {
68 return res.status(304).end();
69 }
70 }
71
72 res.json(content);
73});
74
75function generateETag(data) {
76 // Simple ETag generation (use crypto for production)
77 const hash = require('crypto')
78 .createHash('md5')
79 .update(JSON.stringify(data))
80 .digest('hex');
81 return `"${hash}"`;
82}Nginx: Cache Validation Headers
1# Nginx: Configure cache validation for 304 responses
2server {
3 listen 80;
4 server_name api.example.com;
5
6 location /api/static/ {
7 # Enable ETag
8 etag on;
9
10 # Enable Last-Modified
11 if_modified_since exact;
12
13 # Proxy to backend
14 proxy_pass http://backend;
15 proxy_set_header Host $host;
16
17 # Pass conditional headers
18 proxy_set_header If-None-Match $http_if_none_match;
19 proxy_set_header If-Modified-Since $http_if_modified_since;
20
21 # Backend returns 304 if not modified
22 }
23
24 # Static file serving with 304
25 location /static/ {
26 root /var/www;
27 etag on;
28 if_modified_since exact;
29
30 # Nginx automatically handles 304 for static files
31 }
32
33 # Cache validation
34 location /api/cached/ {
35 proxy_pass http://backend;
36 proxy_cache_valid 200 1h;
37 proxy_cache_valid 304 1h;
38 proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
39
40 # Pass conditional headers
41 proxy_set_header If-None-Match $http_if_none_match;
42 proxy_set_header If-Modified-Since $http_if_modified_since;
43 }
44}↗Related Errors
Provider Information
This error code is specific to HTTP services. For more information, refer to the official HTTP documentation.