One Script Failing Shouldn't Break the Whole Page
When we built the live proposal page for the fire department recruitment project, we had two categories of functionality on the same page. The first category was core: scroll reveal animations, a parallax hero, readable storyboard content. The second category was optional: a Supabase-powered comment system and an in-page approval button. Both lived in the same JavaScript file.
The first version had a problem. Supabase initialization was at the top:
const supabase = window.supabase.createClient(URL, KEY);
If the Supabase CDN script failed to load (network blip, timeout, ad blocker), window.supabase was undefined. That threw immediately. JavaScript stops executing at an uncaught error, so nothing below that line ran. No scroll animations. No parallax. Just a static page with no visual polish, for a proposal that was supposed to sell a $15,000 engagement.
The Fix: Initialize in Priority Order
We restructured the file so scroll animations are defined and called first, before anything touches Supabase:
// ===== Scroll Reveal Animations =====
// These run first — no dependencies, must never fail
function initScrollReveal() { ... }
function initHeroParallax() { ... }
function initTimelineAnimation() { ... }
// ===== Supabase Client =====
// Wrapped in try/catch so animations still work if CDN fails
let sbClient = null;
try {
sbClient = window.supabase.createClient(URL, KEY);
} catch (e) {
console.warn('Supabase failed to load — comments and approvals disabled.', e);
}
Then at DOMContentLoaded, the order is explicit:
document.addEventListener('DOMContentLoaded', () => {
// Animations first — always work
initScrollReveal();
initHeroParallax();
initTimelineAnimation();
// Supabase features — gracefully degrade
if (sbClient) {
COMMENT_SECTIONS.forEach(loadComments);
loadApprovalStatus();
}
});
Every function that talks to Supabase checks if (!sbClient) at the top and bails early. If the client never loaded, the page is still a fully readable, visually polished proposal. The comment forms don't appear. The approve button shows a "temporarily unavailable" message if clicked. Nothing crashes.
The Naming Collision That Made This Concrete
While implementing the try/catch pattern, we initially wrote let supabase = null. The Supabase CDN script declares a global called supabase on window. Using let to redeclare a variable that already exists in the global scope throws a SyntaxError before any code runs. Entire file, dead on arrival, every time.
Renaming our variable to sbClient fixed it. But the collision drove home why the pattern matters: CDN scripts pollute the global namespace. You have to assume they do. Don't name your variables the same thing as libraries you're loading via script tag.
Why This Pattern Applies Beyond Proposals
This is a version of progressive enhancement applied to a client deliverable. The logic is the same as building a site where CSS works before JavaScript loads, or where a form submits without a fancy validation layer if the validation script fails.
For anything client-facing, the question worth asking before you finalize the architecture is: which parts of this page have to work, and which parts are enhancements? The answer drives your initialization order and your error handling strategy.
For us: the proposal content and visual presentation have to work. Comments and approval are valuable but optional. If Supabase is down at 11pm when a fire chief is reviewing the proposal, they should still see a polished page, not a broken one.
The setup takes five minutes to structure correctly. The alternative is a page that fails silently in conditions you can't control.