From 8267a8e64638f22bb97bdc41403716d71dd9b033 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 12 Apr 2026 17:43:17 -0400 Subject: [PATCH] Performance: add GZip compression, externalize CSS, bump cache, reduce workers - Add Starlette GZipMiddleware for response compression (~60% smaller HTML) - Extract 290 lines of inline CSS to /static/site.css (browser-cacheable) - Bump HTML Cache-Control from 5min to 1hr - Reduce Granian workers from 4 to 2 (matches shared-cpu-2x) Co-Authored-By: Claude Opus 4.6 (1M context) --- Dockerfile | 2 +- engine.py | 4 + tuftecms/app.py | 2 +- tuftecms/static/site.css | 282 ++++++++++++++++++++++++++++++++++ tuftecms/templates/base.html | 289 +---------------------------------- 5 files changed, 290 insertions(+), 289 deletions(-) create mode 100644 tuftecms/static/site.css diff --git a/Dockerfile b/Dockerfile index 7b0db76..d28148e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,4 +26,4 @@ RUN uv pip install . --system # Copy the rest of the application COPY . . -CMD ["granian", "--interface", "asgi", "--host", "0.0.0.0", "--port", "8000", "--workers", "4", "--static-path-route", "/static", "--static-path-mount", "tuftecms/static", "--static-path-expires", "604800", "engine:api"] +CMD ["granian", "--interface", "asgi", "--host", "0.0.0.0", "--port", "8000", "--workers", "2", "--static-path-route", "/static", "--static-path-mount", "tuftecms/static", "--static-path-expires", "604800", "engine:api"] diff --git a/engine.py b/engine.py index ad8782a..fb941d6 100644 --- a/engine.py +++ b/engine.py @@ -95,6 +95,10 @@ api = responder.API( description="API for kennethreitz.org", ) +# --- GZip compression for all responses --- +from starlette.middleware.gzip import GZipMiddleware +api.add_middleware(GZipMiddleware, minimum_size=500) + # --- Rate limiting --- from responder.ext.ratelimit import RateLimiter diff --git a/tuftecms/app.py b/tuftecms/app.py index dbbc543..d5efb81 100644 --- a/tuftecms/app.py +++ b/tuftecms/app.py @@ -206,7 +206,7 @@ def register_context_processors(app): # Add cache control for HTML (short cache, must revalidate) if not response.headers.get('Cache-Control'): - response.headers['Cache-Control'] = 'public, max-age=300, must-revalidate' + response.headers['Cache-Control'] = 'public, max-age=3600, must-revalidate' # Inject a script right after to immediately show content content = response.get_data(as_text=True) diff --git a/tuftecms/static/site.css b/tuftecms/static/site.css new file mode 100644 index 0000000..5eb3781 --- /dev/null +++ b/tuftecms/static/site.css @@ -0,0 +1,282 @@ +/* Prevent FOUC by hiding content until stylesheets load */ +html { + visibility: hidden; + opacity: 0; +} + +html.loaded { + visibility: visible; + opacity: 1; + transition: opacity 0.1s ease-in; +} + +/* Legend dots for content guide */ +.legend-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 0.3rem; + vertical-align: middle; +} + +/* Reading progress indicator */ +.reading-progress { + position: fixed; + top: 0; + left: 0; + width: 0%; + height: 2px; + background: #333; + z-index: 1000; + transition: width 0.1s ease-out; +} + +/* Header and nav z-index for dropdown stacking */ +header { + position: relative; + z-index: 9999; +} + +nav { + position: relative; + z-index: 9999; +} + +nav > a, nav > .nav-dropdown { + margin-right: 1rem; +} + +.nav-dropdown { + position: relative; + display: inline-block; +} + +.nav-dropdown-trigger { + cursor: pointer; + color: inherit; + text-decoration: underline; + padding-bottom: 1rem; + display: inline-block; +} + +.nav-dropdown-content { + display: none; + position: absolute; + top: 100%; + left: 0; + background: white; + border: 1px solid #ddd; + border-radius: 4px; + min-width: 250px; + max-width: 400px; + max-height: 500px; + overflow-y: auto; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 10000; + padding: 0.75rem; +} + +.nav-dropdown:hover .nav-dropdown-content, +.nav-dropdown-content:hover { + display: block; +} + +.tree-item { + padding: 0.35rem 0; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.tree-item-icon { + flex-shrink: 0; + width: 18px; + height: 18px; +} + +.tree-item a { + color: #333; + text-decoration: none; + flex: 1; +} + +.tree-item a:hover { + text-decoration: underline; +} + +.tree-folder a { + font-weight: 500; +} + +body.dark-mode .nav-dropdown-content { + background: #1a1a1a; + border-color: #333; +} + +body.dark-mode .tree-item a { + color: #ccc; +} + +@media (prefers-color-scheme: dark) { + .nav-dropdown-content { + background: #1a1a1a; + border-color: #333; + } + + .tree-item a { + color: #ccc; + } +} + +.github-corner { + position: absolute; + top: 0; + right: 0; + z-index: 9998; + opacity: 0.85; + transition: opacity 0.3s ease; +} + +.github-corner:hover { + opacity: 0.8; +} + +.github-corner-img { + width: 80px; + height: 80px; +} + +body.dark-mode .github-corner { + opacity: 0.25; +} + +body.dark-mode .github-corner-img { + filter: invert(1); +} + +@media (max-width: 760px) { + .github-corner-img { + width: 50px; + height: 50px; + } +} + +.theme-toggle { + position: fixed; + bottom: 2rem; + right: 2rem; + background: none; + border: none; + cursor: pointer; + font-size: 1.5rem; + padding: 0.5rem; + margin: 0; + opacity: 0.15; + transition: opacity 0.3s ease; + line-height: 1; + z-index: 9999; +} + +.theme-toggle:hover { + opacity: 0.8; +} + +.sun-icon, +.moon-icon { + display: none; +} + +body.light-mode .sun-icon { + display: inline; +} + +body.dark-mode .moon-icon { + display: inline; +} + +/* Dark mode styles using body class */ +body.dark-mode { + background-color: #0d1117; + color: #c9d1d9; +} + +/* Light mode styles to override media query */ +body.light-mode { + background-color: #fffff8; + color: #111; +} + +@media (max-width: 760px) { + .theme-toggle { + font-size: 1.3rem; + bottom: 1rem; + right: 1rem; + padding: 0.4rem; + } +} + +main { + position: relative; + z-index: 1; +} + +article { + position: relative; + z-index: 1; +} + +/* Dropdown navigation styles */ +.nav-dropdown { + position: relative; + display: inline-block; + padding: 0.5rem; + margin: -0.5rem; + z-index: 9999; +} + +.nav-dropdown .dropdown-content { + display: none; + position: absolute; + top: 100%; + left: 0; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + z-index: 999999; + min-width: 280px; + padding: 0.5rem 0; + margin-top: -1px; +} + +.nav-dropdown:hover .dropdown-content { + display: block; +} + +.nav-dropdown .dropdown-content a { + display: block; + padding: 0.5rem 1rem; + color: #333; + text-decoration: none; + border-bottom: none; + font-size: 0.9rem; + line-height: 1.4; +} + +.nav-dropdown .dropdown-content a:hover { + background-color: #f5f5f5; +} + +.nav-dropdown .dropdown-content a .index-description { + display: block; + font-size: 0.75rem; + color: #666; + margin-top: 0.1rem; +} + +.nav-dropdown > a::after { + content: "\00a0\25BC"; + font-size: 0.7rem; + color: #999; +} diff --git a/tuftecms/templates/base.html b/tuftecms/templates/base.html index ae43b1b..71b3cc8 100644 --- a/tuftecms/templates/base.html +++ b/tuftecms/templates/base.html @@ -49,293 +49,8 @@ - + +