Skip to content

Instantly share code, notes, and snippets.

@eins78
Last active February 23, 2026 16:40
Show Gist options
  • Select an option

  • Save eins78/91149c65be1fa57421f923e6fc14f483 to your computer and use it in GitHub Desktop.

Select an option

Save eins78/91149c65be1fa57421f923e6fc14f483 to your computer and use it in GitHub Desktop.

Fix: CSS Cache-Control Headers

Date: 2026-02-23 Branch: fix/cache-control-headersdevelop PR: #279

Problem

Static resources served with Cache-Control: no-cache, no-store, max-age=0, must-revalidate because custom ResourceHandler registrations never set cache headers, and Spring Security fills in aggressive defaults.

How to verify yourself

1. Start the app

export spring_profiles_active=test,jwt-decoder-in-memory,backoffice-jwt-decoder-no-auth,sap-in-memory,email-in-memory
./gradlew :backend:springboot-app:bootRun -x test -x :frontend:dist -x copyFrontend -x copyStorybook

Uses H2 in-memory DB so no PostgreSQL needed. You may need dummy files under build/resources/main/public/{theme,portal,backoffice}/ if the frontend hasn't been built.

2. Check headers manually with curl

# Theme CSS — served by /wvz-rs/ui/theme/** handler
curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css

# Portal CSS — served by /wvz-rs/ui/** handler
curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/wvz-design.css

# Backoffice theme — served by /wvz-bo/ui/theme/** handler
curl -s -D - -o /dev/null http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css

# API/health endpoint — should still be no-cache (not a static resource)
curl -s -D - -o /dev/null http://localhost:8080/health

Note: Use curl -s -D - -o /dev/null, NOT curl -I. The -I flag sends a HEAD request, which Spring Security blocks (only GET is permitAll()).

3. Or run the test script

#!/usr/bin/env bash
# test-cache-headers.sh — Usage: ./test-cache-headers.sh [base_url]
set -uo pipefail

BASE="${1:-http://localhost:8080}"
PASS=0
FAIL=0

assert_cache_header() {
    local url="$1"
    local expected="$2"
    local label="$3"

    local headers
    headers=$(curl -s -D - -o /dev/null "$url" 2>/dev/null)
    local status
    status=$(echo "$headers" | head -1 | tr -d '\r')
    local actual
    actual=$(echo "$headers" | grep -i "^cache-control:" | sed 's/^[Cc]ache-[Cc]ontrol: //' | tr -d '\r')

    if [[ "$actual" == "$expected" ]]; then
        echo "PASS: $label"
        echo "      $url"
        echo "      $status → Cache-Control: $actual"
        PASS=$((PASS + 1))
    else
        echo "FAIL: $label"
        echo "      $url"
        echo "      $status"
        echo "      expected: Cache-Control: $expected"
        echo "      actual:   Cache-Control: $actual"
        FAIL=$((FAIL + 1))
    fi
    echo
}

echo "=== Cache-Control Header Tests ==="
echo "    Base URL: $BASE"
echo

assert_cache_header \
    "$BASE/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css" \
    "max-age=300, public" \
    "Theme CSS (webbloqs) should be cached 5 min"

assert_cache_header \
    "$BASE/wvz-rs/ui/wvz-design.css" \
    "max-age=300, public" \
    "Portal CSS (wvz-design) should be cached 5 min"

assert_cache_header \
    "$BASE/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css" \
    "max-age=300, public" \
    "Backoffice theme CSS should be cached 5 min"

echo "=== Results: $PASS passed, $FAIL failed ==="
[[ $FAIL -eq 0 ]]

Evidence

RED — before fix (on develop, commit 628fb88f)

$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate

$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/wvz-design.css
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate

$ curl -s -D - -o /dev/null http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate

Test script output:

=== Cache-Control Header Tests ===
    Base URL: http://localhost:8080

FAIL: Theme CSS (webbloqs) should be cached 5 min
      http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
      HTTP/1.1 200
      expected: Cache-Control: max-age=300, public
      actual:   Cache-Control: no-cache, no-store, max-age=0, must-revalidate

FAIL: Portal CSS (wvz-design) should be cached 5 min
      http://localhost:8080/wvz-rs/ui/wvz-design.css
      HTTP/1.1 200
      expected: Cache-Control: max-age=300, public
      actual:   Cache-Control: no-cache, no-store, max-age=0, must-revalidate

FAIL: Backoffice theme CSS should be cached 5 min
      http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
      HTTP/1.1 200
      expected: Cache-Control: max-age=300, public
      actual:   Cache-Control: no-cache, no-store, max-age=0, must-revalidate

=== Results: 0 passed, 3 failed ===

GREEN — after fix (commit 00219c69)

$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: max-age=300, public

$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/wvz-design.css
HTTP/1.1 200
Cache-Control: max-age=300, public

$ curl -s -D - -o /dev/null http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: max-age=300, public

$ curl -s -D - -o /dev/null http://localhost:8080/health
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate   ← API still no-cache, correct

Test script output:

=== Cache-Control Header Tests ===
    Base URL: http://localhost:8080

PASS: Theme CSS (webbloqs) should be cached 5 min
      http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
      HTTP/1.1 200 → Cache-Control: max-age=300, public

PASS: Portal CSS (wvz-design) should be cached 5 min
      http://localhost:8080/wvz-rs/ui/wvz-design.css
      HTTP/1.1 200 → Cache-Control: max-age=300, public

PASS: Backoffice theme CSS should be cached 5 min
      http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
      HTTP/1.1 200 → Cache-Control: max-age=300, public

=== Results: 3 passed, 0 failed ===

Files changed

File Change
backend/.../WebResourceConfiguration.java Added .setCacheControl(CacheControl.maxAge(5min).cachePublic()) to all 5 handlers
application.yml Removed dead spring.web.resources.cache block
application-dev.yml Removed dead spring.web.resources.cache block
application-quatico-test.yml Removed dead spring.web.resources.cache block
backend/.../MagellanRestController.java Fixed pre-existing compile error: getFirst()get(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment