Skip to content

perf: defer athena.js and all.js footer scripts#13133

Draft
lokesh wants to merge 1 commit into
masterfrom
claude/perf/defer-footer-scripts
Draft

perf: defer athena.js and all.js footer scripts#13133
lokesh wants to merge 1 commit into
masterfrom
claude/perf/defer-footer-scripts

Conversation

@lokesh

@lokesh lokesh commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #

Performance fix: takes the two main JS payloads off the HTML parser's critical path on every page, and lets the browser schedule their fetches earlier.

Estimated performance gain

Roughly 50 ms (fast broadband) to 1 s+ (slow 3G) of parser-blocking time removed at the end of every page load, on every page. Today the parser stalls at the two script tags until athena.js (external-upstream-proxied) and all.js (~155 KB gz) download and execute; with defer the parser finishes the document (including the analytics div and closing markup) without waiting, and both fetches can be scheduled by the preload scanner during parse. Estimates below assume the ~155 KB gz all.js plus a typical proxied athena response; connection-level numbers are ballparks by network profile, not measurements:

xychart-beta
    title "Est. parser-blocking script time removed per page (ms)"
    x-axis ["Fast broadband", "4G", "Slow 3G"]
    y-axis "blocked ms (estimate)" 0 --> 1200
    bar [50, 400, 1100]
Loading

Two caveats for honesty: (1) the tags sit at the end of <body>, so first paint of above-the-fold content was already unblocked — the win is in DOM-complete/DOMContentLoaded timing, tail-of-page content, and resilience to a slow archive.org analytics upstream (previously a hung athena.js held the parser and all.js hostage; now it cannot delay parsing at all). (2) Deferred scripts still execute in document order, so a slow athena still delays all.js execution — full decoupling needs the #4474 follow-up. Additionally, server_ms analytics (previously always empty due to a read-before-parse bug) starts reporting real values — a measurement gain rather than a speed gain.

Technical

Problem. templates/site/footer.html loaded both scripts synchronously:

<script src="/cdn/archive.org/athena.js" type="text/javascript"></script>
<script src="$static_url('build/js/all.js')" type="text/javascript"></script>
  • /cdn/archive.org/athena.js is proxied through OL's own FastAPI (openlibrary/fastapi/cdn.py) to archive.org. As a synchronous script it blocks everything after it — including all.js (~155 KB gz: jQuery, jquery-ui, colorbox, Sentry, core OL code) — on an external upstream's response time.
  • Both scripts block parsing of the remainder of the document while they download and execute.

Why not just async/defer on athena.js alone — the load-bearing detail: there is a real synchronous ordering dependency. initAnalytics() (js/ol.analytics.js, called at all.js execution — the code itself notes it "needs to be run synchronously" and warns about #4474) requires window.archive_analytics to already exist, and js/carousel/Carousel.js calls window.archive_analytics.ol_send_event_ping(...) unguarded. Making athena.js async, or deferring it while all.js stays synchronous, can execute all.js first and break event tracking (and throw in the carousel handlers).

Fix: defer on both tags, as a pair. Deferred classic scripts execute in document order (athena → all.js) and before DOMContentLoaded, so:

  • the window.archive_analytics ordering contract is preserved;
  • the send_pageview({}) DOMContentLoaded hook in ol.analytics.js still fires after both have run;
  • the ol-components.js module tag (already implicitly deferred) still executes after them, unchanged.

Bonus correctness fix: initAnalytics reads $('.analytics-stats-time-calculator').data('time') for the server_ms stat, but that div sits after the all.js tag in the footer — with synchronous execution it wasn't parsed yet at read time. With defer, execution happens after the full parse, so the value is actually available.

Testing

  • make test-py-uv PYTEST_ARGS="openlibrary/tests/test_templates.py" — 763 passed (template render smoke tests).
  • pre-commit run --files openlibrary/templates/site/footer.html — all hooks pass.

Manual verification steps for reviewers (devtools):

  1. Load any page; in the Network/Performance panel confirm athena.js and all.js are fetched with defer semantics and execute in order (athena first), before DOMContentLoaded.
  2. Confirm analytics pageview + event pings still fire (_paq/athena network beacons).
  3. Interact with a homepage carousel (next/swipe) — no ol_send_event_ping console errors.
  4. Confirm server_ms is now populated in the analytics ping (previously read before the div was parsed).

Screenshot

N/A — no visual change.

Stakeholders

Part of a performance-review series; see the review report branch claude/performance-review-top-changes-5wcs7g.

🤖 Generated with Claude Code

https://claude.ai/code/session_015ky5Sc19MGPkDgbgTWWWET

Both scripts loaded synchronously at the end of body: athena.js
(analytics, proxied to archive.org) blocked all.js on its download,
and both blocked the parser for the tail of the document.

Add defer to both as a pair. defer preserves document order, which
matters because initAnalytics in all.js requires
window.archive_analytics from athena.js at execution time
(Carousel.js also calls ol_send_event_ping unguarded), so athena.js
must never be deferred or made async alone. Deferred scripts run
before DOMContentLoaded, so the send_pageview hook in ol.analytics.js
is unaffected. This also fixes the server_ms stat: initAnalytics reads
.analytics-stats-time-calculator, which is parsed after the old
synchronous all.js tag executed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ky5Sc19MGPkDgbgTWWWET
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants