127.0.0.1 - - [17/Nov/2025 16:03:28] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [17/Nov/2025 16:03:39] "GET / HTTP/1.1" 304 -
127.0.0.1 - - [17/Nov/2025 16:04:15] "GET / HTTP/1.1" 304 -
- First request: Returns 200 (OK) - Full response sent
- Subsequent requests: Return 304 (Not Modified) - Browser uses cached version
Flask's send_from_directory() automatically implements HTTP caching using:
-
ETag Header: A hash/fingerprint of the file content
- Browser sends
If-None-Matchheader with the ETag - Server compares, returns 304 if file unchanged
- Browser sends
-
Last-Modified Header: File's last modification timestamp
- Browser sends
If-Modified-Sinceheader - Server compares, returns 304 if file not modified
- Browser sends
In Dashboard/server.py:
@app.route('/')
def index():
"""Serve the main dashboard"""
return send_from_directory('.', 'dashboard.html')Flask's send_from_directory() automatically adds:
ETagheader (content hash)Last-Modifiedheader (file mtime)Cache-Controlheader (default: public)
-
First Request:
GET / HTTP/1.1 Host: localhost:8000Response:
HTTP/1.1 200 OK ETag: "abc123" Last-Modified: Mon, 17 Nov 2025 16:00:00 GMT Content-Length: 12345 [full content] -
Second Request (browser includes caching headers):
GET / HTTP/1.1 Host: localhost:8000 If-None-Match: "abc123" If-Modified-Since: Mon, 17 Nov 2025 16:00:00 GMTResponse:
HTTP/1.1 304 Not Modified ETag: "abc123" Last-Modified: Mon, 17 Nov 2025 16:00:00 GMT [no body - browser uses cache]
No - This is the correct and expected behavior for static file serving:
✅ Benefits:
- Reduces bandwidth usage
- Faster page loads (no content transfer)
- Less server processing
- Better user experience
- You're actively developing and want fresh content every time
- You need to bypass cache for testing
- Users need to see updates immediately
Add cache control headers to prevent caching:
from flask import Flask, send_from_directory, make_response
@app.route('/')
def index():
"""Serve the main dashboard"""
response = make_response(send_from_directory('.', 'dashboard.html'))
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return responseForce browser to fetch fresh content:
- Chrome/Firefox:
Ctrl + Shift + RorCmd + Shift + R(Mac) - All browsers:
Ctrl + F5
Enable caching in production, disable in development:
@app.route('/')
def index():
"""Serve the main dashboard"""
response = make_response(send_from_directory('.', 'dashboard.html'))
# Disable caching in debug mode
if app.debug:
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
else:
# Allow caching in production (default Flask behavior)
response.headers['Cache-Control'] = 'public, max-age=300' # 5 minutes
return responseAdd version parameter to URLs:
<!-- In dashboard.html -->
<script src="script.js?v=1.2.3"></script>
<link rel="stylesheet" href="style.css?v=1.2.3">Changing the version forces browser to fetch new file.
Use the provided test script:
python3 test_caching.pyOr use curl to inspect headers:
# First request
curl -I http://localhost:8000/
# Conditional request (should get 304)
ETAG=$(curl -s -I http://localhost:8000/ | grep -i etag | cut -d' ' -f2)
curl -I -H "If-None-Match: $ETAG" http://localhost:8000/-
For Production: Keep caching enabled (current behavior is optimal)
-
For Development:
- Use
--debugflag when starting server - Use hard refresh in browser
- Or disable caching for development mode
- Use
-
For Active Development: Add
--no-cacheflag option to server.py