feat: add resilience layer with caching and graceful degradation for …#2
Conversation
…Supabase outages see website/#1
There was a problem hiding this comment.
Pull request overview
This PR implements a resilience layer to handle Supabase outages gracefully by adding caching and fallback mechanisms.
Key Changes
- Implements a caching strategy that pre-fetches signatories data during build/start and stores it locally
- Adds graceful degradation with automatic fallback to cached data when Supabase is unavailable
- Includes a testing mode to simulate Supabase outages via environment variable
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/pages/signatories.tsx |
Enables error banner display on the signatories page |
src/components/SignersList.tsx |
Implements core resilience logic with error handling and cache fallback |
src/components/SignersList.module.css |
Adds styling for error banner showing cache status |
src/components/SignManifest.tsx |
Adds outage detection and disables signing when Supabase is unavailable |
src/components/SignManifest.module.css |
Adds styling for warning banner |
scripts/cache-signatories.cjs |
Creates script to fetch and cache signatories data from Supabase |
package.json |
Integrates cache script into prebuild and prestart hooks |
docusaurus.config.ts |
Adds configuration for simulating Supabase outages |
.gitignore |
Excludes generated cache file from version control |
.env |
Adds simulation toggle environment variable |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| console.log('🔄 Fetching signatories from Supabase...'); | ||
|
|
||
| // Use fetch API to query Supabase REST API directly | ||
| const response = await fetch( |
There was a problem hiding this comment.
The script uses the global fetch API which is only available in Node.js 18+. Consider adding a comment documenting the minimum Node.js version requirement, or add a runtime check with a helpful error message if fetch is not available.
| window.addEventListener('signature-changed', handler); | ||
| return () => window.removeEventListener('signature-changed', handler); | ||
| }, [supabase]); | ||
| }, [supabase, simulateDown]); |
There was a problem hiding this comment.
The dependency array is missing shouldUseMockData. Since shouldUseMockData is calculated from USE_MOCK_DATA and simulateDown, and simulateDown is included in the dependency array, consider adding shouldUseMockData to the dependency array or simplifying by using simulateDown directly in the condition on line 45.
| {cachedSignatories.cached_at && ( | ||
| <span className={styles.cacheDate}> | ||
| {' '} | ||
| (as of {new Date(cachedSignatories.cached_at).toLocaleString('en-US')}) | ||
| </span> | ||
| )} |
There was a problem hiding this comment.
Missing error handling if cachedSignatories.cached_at contains an invalid date string. Consider wrapping the new Date() call in a try-catch or validating the date before rendering to prevent potential runtime errors.
| {cachedSignatories.cached_at && ( | |
| <span className={styles.cacheDate}> | |
| {' '} | |
| (as of {new Date(cachedSignatories.cached_at).toLocaleString('en-US')}) | |
| </span> | |
| )} | |
| {cachedSignatories.cached_at && (() => { | |
| const cacheDate = new Date(cachedSignatories.cached_at); | |
| if (!isNaN(cacheDate.getTime())) { | |
| return ( | |
| <span className={styles.cacheDate}> | |
| {' '} | |
| (as of {cacheDate.toLocaleString('en-US')}) | |
| </span> | |
| ); | |
| } | |
| return ( | |
| <span className={styles.cacheDate}> | |
| {' '} | |
| (as of unknown date) | |
| </span> | |
| ); | |
| })()} |
| {showErrorBanner && error && usingCache && ( | ||
| <div className={styles.errorBanner}> | ||
| {error}. Showing cached signatories | ||
| {cachedSignatories.cached_at && ( | ||
| <span className={styles.cacheDate}> | ||
| {' '} | ||
| (as of {new Date(cachedSignatories.cached_at).toLocaleString('en-US')}) | ||
| </span> |
There was a problem hiding this comment.
The error banner only displays when both error AND usingCache are true. This means if there's an error but no cached data is available (lines 89-91), no error message is shown to the user. Consider showing an error message even when cached data is not available, so users are informed of the issue.
| {showErrorBanner && error && usingCache && ( | |
| <div className={styles.errorBanner}> | |
| {error}. Showing cached signatories | |
| {cachedSignatories.cached_at && ( | |
| <span className={styles.cacheDate}> | |
| {' '} | |
| (as of {new Date(cachedSignatories.cached_at).toLocaleString('en-US')}) | |
| </span> | |
| {showErrorBanner && error && ( | |
| <div className={styles.errorBanner}> | |
| {error}. | |
| {usingCache ? ( | |
| <> | |
| {' '}Showing cached signatories | |
| {cachedSignatories.cached_at && ( | |
| <span className={styles.cacheDate}> | |
| {' '} | |
| (as of {new Date(cachedSignatories.cached_at).toLocaleString('en-US')}) | |
| </span> | |
| )} | |
| </> | |
| ) : ( | |
| <> No cached data available.</> |
| SUPABASE_URL=https://cuwxnggcavvzbqbidaml.supabase.co | ||
| SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImN1d3huZ2djYXZ2emJxYmlkYW1sIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjMzMTA5NzAsImV4cCI6MjA3ODg4Njk3MH0.X0BjHpf6hR3v3Iwv7BN5wkJhewLTzCZnbTq090vuGi0 | ||
| SIMULATE_SUPABASE_DOWN=false No newline at end of file |
There was a problem hiding this comment.
The .env file should not be committed to version control as it contains sensitive credentials. Add .env to .gitignore and create a .env.example file with placeholder values to document required environment variables.
…Supabase outages
see website/#1