Documentation

I am getting this error on the lighthouse report.

"uses-long-cache-ttl": {
  "id": "uses-long-cache-ttl",
  "title": "Serve static assets with an efficient cache policy",
  "description": "A long cache lifetime can speed up repeat visits to your page. [Learn more about efficient cache policies](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/).",
  "score": 0.5,
  "scoreDisplayMode": "metricSavings",
  "numericValue": 14606.099999999997,
  "numericUnit": "byte",
  "displayValue": "30 resources found",
  "details": {
    "type": "table",
    "headings": [
      {
        "key": "url",
        "valueType": "url",
        "label": "URL"
      },
      {
        "key": "cacheLifetimeMs",
        "valueType": "ms",
        "label": "Cache TTL",
        "displayUnit": "duration"
      },
      {
        "key": "totalBytes",
        "valueType": "bytes",
        "label": "Transfer Size",
        "displayUnit": "kb",
        "granularity": 1
      }
    ],
    "items": [
      {
        "url": "https://seriousdesign.net/js/pwa-manager.js",
        "cacheLifetimeMs": 0,
        "cacheHitProbability": 0,
        "totalBytes": 3296,
        "wastedBytes": 3296
      },
      {
        "url": "https://seriousdesign.net/js/css-loader.js",
        "cacheLifetimeMs": 0,
        "cacheHitProbability": 0,
        "totalBytes": 1875,
        "wastedBytes": 1875
      },
      {
        "url": "https://seriousdesign.net/js/critical.js",
        "cacheLifetimeMs": 0,
        "cacheHitProbability": 0,
        "totalBytes": 1179,
        "wastedBytes": 1179
      },
      {
        "url": "https://seriousdesign.net/js/performance-monitor.js",
        "cacheLifetimeMs": 0,
        "cacheHitProbability": 0,
        "totalBytes": 0,
        "wastedBytes": 0
      },
      {
        "url": "https://seriousdesign.net/js/script.js",
        "cacheLifetimeMs": 0,
        "cacheHitProbability": 0,
        "totalBytes": 0,
        "wastedBytes": 0
      },
      {
        "url": "https://seriousdesign.net/css/style.css",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 9434,
        "wastedBytes": 943.3999999999997
      },
      {
        "url": "https://seriousdesign.net/img/logos/linux.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 9411,
        "wastedBytes": 941.0999999999998
      },
      {
        "url": "https://seriousdesign.net/img/logos/php.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 5687,
        "wastedBytes": 568.6999999999998
      },
      {
        "url": "https://seriousdesign.net/img/logos/playwright.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 5506,
        "wastedBytes": 550.5999999999999
      },
      {
        "url": "https://seriousdesign.net/img/logos/excalidraw.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 5410,
        "wastedBytes": 540.9999999999999
      },
      {
        "url": "https://seriousdesign.net/img/logos/drupal.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 4148,
        "wastedBytes": 414.7999999999999
      },
      {
        "url": "https://seriousdesign.net/img/logos/project-management.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 4121,
        "wastedBytes": 412.0999999999999
      },
      {
        "url": "https://seriousdesign.net/img/logos/html.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 3290,
        "wastedBytes": 328.99999999999994
      },
      {
        "url": "https://seriousdesign.net/img/logos/node.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 3071,
        "wastedBytes": 307.0999999999999
      },
      {
        "url": "https://seriousdesign.net/img/logos/infrastructure.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 3010,
        "wastedBytes": 300.99999999999994
      },
      {
        "url": "https://seriousdesign.net/img/logos/css.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 2975,
        "wastedBytes": 297.49999999999994
      },
      {
        "url": "https://seriousdesign.net/img/logos/sketchup.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 2732,
        "wastedBytes": 273.19999999999993
      },
      {
        "url": "https://seriousdesign.net/img/logos/htmx.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 2589,
        "wastedBytes": 258.8999999999999
      },
      {
        "url": "https://seriousdesign.net/img/logos/excel.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 2582,
        "wastedBytes": 258.19999999999993
      },
      {
        "url": "https://seriousdesign.net/img/logos/software-development.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 2222,
        "wastedBytes": 222.19999999999996
      },
      {
        "url": "https://seriousdesign.net/img/logos/js.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 2177,
        "wastedBytes": 217.69999999999996
      },
      {
        "url": "https://seriousdesign.net/img/logos/symfony.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1966,
        "wastedBytes": 196.59999999999997
      },
      {
        "url": "https://seriousdesign.net/img/logos/wordpress.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1959,
        "wastedBytes": 195.89999999999995
      },
      {
        "url": "https://seriousdesign.net/img/logos/firmware-development.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1958,
        "wastedBytes": 195.79999999999995
      },
      {
        "url": "https://seriousdesign.net/img/logos/software-architecture.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1940,
        "wastedBytes": 193.99999999999994
      },
      {
        "url": "https://seriousdesign.net/img/logos/python.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1621,
        "wastedBytes": 162.09999999999997
      },
      {
        "url": "https://seriousdesign.net/img/logos/figma.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1444,
        "wastedBytes": 144.39999999999998
      },
      {
        "url": "https://seriousdesign.net/img/logos/kirby.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1305,
        "wastedBytes": 130.49999999999997
      },
      {
        "url": "https://seriousdesign.net/img/logos/docker.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 1024,
        "wastedBytes": 102.39999999999998
      },
      {
        "url": "https://seriousdesign.net/img/logos/apify.svg",
        "debugData": {
          "type": "debugdata",
          "max-age": 604800
        },
        "cacheLifetimeMs": 604800000,
        "cacheHitProbability": 0.9,
        "totalBytes": 979,
        "wastedBytes": 97.89999999999998
      }
    ],
    "summary": {
      "wastedBytes": 14606.099999999997
    },
    "sortedBy": [
      "totalBytes"
    ],
    "skipSumming": [
      "cacheLifetimeMs"
    ]
  },
  "guidanceLevel": 3
},

It is a project built with symfony components, mounted on my hosttinger vps using apache 2

Perfect, this is super clear. Let’s break down what Lighthouse is telling you, why it matters, and what you should actually change in your Apache config on Hostinger for a Symfony-components site.


1. What Lighthouse is complaining about

Lighthouse audit: "Serve static assets with an efficient cache policy"

Translation:

  • Your static files (JS, CSS, SVG, etc.) should tell the browser:

    • "Hey, you can keep this file in cache for a long time, don't re-download it every visit."
  • If you don’t, repeat visits reload everything = slower page + worse score.

The audit looks at two things for each file:

  • cacheLifetimeMs → how long the browser is allowed to cache it, based on your HTTP headers.
  • totalBytes → size of that file.
  • wastedBytes → “how many bytes could have been saved on repeat visits if you cached this long-term.”

Interpreting your data

Group A: JS with cacheLifetimeMs: 0

/js/pwa-manager.js
/js/css-loader.js
/js/critical.js
/js/performance-monitor.js
/js/script.js

These files are served with no caching at all (no Cache-Control / Expires or explicitly max-age=0).

That means: browser downloads them every page load.

👉 Lighthouse hates that.


Group B: CSS + SVGs with cacheLifetimeMs: 604800000

Example:

/css/style.css
/img/logos/*.svg

You are sending a caching header here:

max-age = 604800 seconds  = 7 days

Lighthouse is still nagging, even though it's already 7 days.

Why? Two reasons:

  1. Lighthouse is greedy. It prefers something like cache-control: public, max-age=31536000, immutable (≈ 1 year) for static assets that rarely change.
  2. Your assets are not fingerprinted (I’ll explain this in a sec), so Lighthouse assumes they might change and then the browser might keep an old version.

So: 7 days is “ok but could be longer,” 0 is “bad.”


2. What you SHOULD do (high level)

For static assets that don't change often → send long cache headers (weeks/months).

For assets that change often → you have 2 options:

  • either don’t cache them long (what you have now)
  • or start using cache busting / fingerprinting so you can cache them long

Let’s go step by step.


3. Fix #1 — Add long-lived caching headers in Apache

Because you’re on Apache 2 on Hostinger (not Symfony HttpKernel serving assets directly), the easiest fix is at Apache level.

a. Enable mod_expires and mod_headers

On a VPS you control Apache, so as root:

a2enmod expires
a2enmod headers
systemctl restart apache2

If you don’t have systemctl on Hostinger shared (only service restarts via panel), just make sure expires and headers are already enabled. On a VPS you’re fine.


b. Add caching rules for static files

You can do this either in the <VirtualHost> block for seriousdesign.net or in a .htaccess in your public web root (probably /var/www/.../public or similar, wherever Apache DocumentRoot points to).

Here’s a good starting point:

<IfModule mod_expires.c>
    ExpiresActive On

    # Images, fonts, SVGs: cache for 1 year
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"

    # CSS and JS: cache for 1 month
    # (We’ll talk in a moment about how to safely make this 1 year)
    ExpiresByType text/css "access plus 30 days"
    ExpiresByType application/javascript "access plus 30 days"
    ExpiresByType text/javascript "access plus 30 days"
</IfModule>

<IfModule mod_headers.c>
    # Let browser know it's allowed to cache
    <FilesMatch "\.(js|css)$">
        Header set Cache-Control "public, max-age=2592000"   # 30 days
    </FilesMatch>

    <FilesMatch "\.(svg|png|jpe?g|gif|webp|woff2?|ttf|otf)$">
        Header set Cache-Control "public, max-age=31536000, immutable"  # 1 year
    </FilesMatch>
</IfModule>

What this does:

  • Adds Expires and Cache-Control headers for each type.
  • SVG logos like img/logos/php.svg will now get ~1 year cache.
  • Lighthouse will be much happier.

👉 You’ll notice we’re giving CSS/JS only 30 days here, not 1 year. That’s intentional for now.

Why: if you update /css/style.css but keep the same URL, browsers might keep the old one for a full year = broken layout.

So we play it safer until we solve cache busting.


4. Fix #2 — Add cache busting (a.k.a. fingerprinting / versioned filenames)

This is how you get Lighthouse “green perfection mode.”

Problem right now:

  • You’re serving plain static paths like /css/style.css, /js/pwa-manager.js.
  • If you cache that for 1 year and then you edit the file, old users won’t get the update quickly.

Solution:

  • When you deploy, generate files with a hash or version in the filename.

    • Example: style.ab12c3.css instead of style.css
    • Example: pwa-manager.20251101.js instead of pwa-manager.js
  • Then update your HTML templates to load that file.

Why this helps:

  • You can now safely tell the browser “cache this for A YEAR, NEVER DOWNLOAD AGAIN” because

    • when you change the file, it’ll have a new URL → browser treats it as a new resource.

Then you can raise JS/CSS cache to 1 year too:

<FilesMatch "\.(js|css)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

This will crush Lighthouse’s complaint.

How to do versioning in your stack depends on how you're building assets:

  • If you’re already running a build step (Webpack, Vite, etc.) → it can output hashed filenames automatically.

  • If you’re serving hand-written JS/CSS (no bundler yet) → you can fake it short-term by adding a query string like style.css?v=20251101.

    • Browsers cache per full URL including ?v=...
    • Some CDNs/proxies are picky, but for most normal browsers it works.
    • Lighthouse usually accepts long cache + query param versioning as "good enough" for MVPs.

5. Why some files show 0 bytes and totalBytes: 0

For:

/js/performance-monitor.js
/js/script.js

totalBytes: 0

That’s Lighthouse saying “you referenced this file but I didn’t really download meaningful content / it was maybe empty or 0 bytes”. Could be:

  • The file exists but is empty/minified to nothing.
  • The audit logic couldn't determine size because of response headers (happens with redirects, inline streaming, or 404 → 200 fallbacks).

Still, cacheLifetimeMs: 0 means Apache sent no caching headers. So they’re still being flagged.


6. Minimal actionable plan for you

Step 1. Add the Apache rules above (mod_expires + mod_headers) either in VirtualHost or .htaccess where your static assets live.

Step 2. Redeploy and re-run Lighthouse. You should already see a better score because:

  • Before: JS = 0 seconds
  • After: JS = ~30 days
  • Before: SVGs = 7 days
  • After: SVGs = 1 year

Step 3. (Optional but pro) Start adding versioning to your asset filenames (hash or ?v=). Then safely increase JS/CSS cache to 1 year too.

This is basically what well-tuned Symfony/AssetMapper or Webpack Encore does automatically: hashed filenames + long immutable cache headers.


7. Quick copy/paste for .htaccess

If you just want a drop-in .htaccess in your public web root, use this:

<IfModule mod_expires.c>
    ExpiresActive On

    # Images, fonts, SVGs: 1 year
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"

    # CSS / JS: 30 days for now
    ExpiresByType text/css "access plus 30 days"
    ExpiresByType application/javascript "access plus 30 days"
    ExpiresByType text/javascript "access plus 30 days"
</IfModule>

<IfModule mod_headers.c>
    <FilesMatch "\.(js|css)$">
        Header set Cache-Control "public, max-age=2592000"
    </FilesMatch>

    <FilesMatch "\.(svg|png|jpe?g|gif|webp|woff2?|ttf|otf)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>
</IfModule>

Put that in the directory Apache is serving (where /css and /js and /img live). No PHP changes needed.


8. TL;DR for ProtonSystems-style “Site Audit” reports ✅

When you report this to a client (or to yourself in a Site Audit PDF 😎), you can phrase it like this:

  • Problem: Static assets are either not cached (0 seconds) or cached only ~7 days. This causes unnecessary downloads on repeat visits and hurts performance scores.

  • Impact: Repeat visitors reload JavaScript, CSS, and icons instead of using the browser cache.

  • Recommendation:

    1. Enable Apache’s mod_expires and mod_headers.
    2. Add long-term caching headers (Cache-Control: public, max-age=X, immutable) for static assets.
    3. Introduce cache-busting (versioned filenames like style.abc123.css) so we can safely cache for 1 year.

That’s literally Lighthouse’s ask.


If you paste me your current <VirtualHost> for seriousdesign.net or the current .htaccess in your public dir, I can show you the exact final version with no guessing.

Contents