my website at ewancroft.uk
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: add request timeouts and fix stale routes causing 500s

- Add 8s timeout wrappers to homepage and layout fetch calls
- Remove stale /src/app.css link from app.html
- Add /nodeinfo/2.0 redirect to ap.ewancroft.uk

+68 -20
-1
src/app.html
··· 1 1 <!doctype html> 2 2 <html lang="en"> 3 3 <head> 4 - <link rel="stylesheet" href="/src/app.css" /> 5 4 <link rel="preload" href="/fonts/InterVariable.woff2" as="font" type="font/woff2" crossorigin /> 6 5 <link rel="preload" href="/fonts/InterVariable-Italic.woff2" as="font" type="font/woff2" crossorigin /> 7 6 <meta charset="utf-8" />
+22 -1
src/routes/+layout.ts
··· 3 3 import { fetchProfile } from '$lib/services/atproto'; 4 4 5 5 /** 6 + * Wraps a promise with a timeout. Returns null on timeout or rejection. 7 + */ 8 + function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | null> { 9 + return new Promise((resolve) => { 10 + const timer = setTimeout(() => resolve(null), ms); 11 + promise.then( 12 + (value) => { 13 + clearTimeout(timer); 14 + resolve(value); 15 + }, 16 + () => { 17 + clearTimeout(timer); 18 + resolve(null); 19 + } 20 + ); 21 + }); 22 + } 23 + 24 + const REQUEST_TIMEOUT = 8_000; 25 + 26 + /** 6 27 * Layout load function - fetches profile data and provides base site metadata 7 28 */ 8 29 export const load: LayoutLoad = async ({ url, fetch }) => { ··· 15 36 // Fetch profile data (needed by Header and page components) 16 37 let profile = null; 17 38 try { 18 - profile = await fetchProfile(fetch); 39 + profile = await withTimeout(fetchProfile(fetch), REQUEST_TIMEOUT); 19 40 } catch (error) { 20 41 console.error('[Layout] Failed to load profile:', error); 21 42 }
+29 -6
src/routes/+page.ts
··· 10 10 fetchRecentPopfeedReviews 11 11 } from '$lib/services/atproto'; 12 12 13 + /** 14 + * Wraps a promise with a timeout. Returns null on timeout or rejection, 15 + * so it's safe to use with Promise.allSettled. 16 + */ 17 + function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | null> { 18 + return new Promise((resolve) => { 19 + const timer = setTimeout(() => resolve(null), ms); 20 + promise.then( 21 + (value) => { 22 + clearTimeout(timer); 23 + resolve(value); 24 + }, 25 + () => { 26 + clearTimeout(timer); 27 + resolve(null); 28 + } 29 + ); 30 + }); 31 + } 32 + 33 + /** Per-request timeout — keeps Vercel serverless functions under the 10s limit. */ 34 + const REQUEST_TIMEOUT = 8_000; 35 + 13 36 export const load: PageLoad = async ({ fetch, parent }) => { 14 37 const { profile } = await parent(); 15 38 16 39 const [musicStatus, kibunStatus, latestPost, documents, supporters, popfeedReview] = 17 40 await Promise.allSettled([ 18 - fetchMusicStatus(fetch), 19 - fetchKibunStatus(fetch), 20 - fetchLatestBlueskyPost(fetch), 21 - fetchRecentDocuments(5, fetch), 22 - fetchAllSupporters(), 23 - fetchRecentPopfeedReviews(fetch) 41 + withTimeout(fetchMusicStatus(fetch), REQUEST_TIMEOUT), 42 + withTimeout(fetchKibunStatus(fetch), REQUEST_TIMEOUT), 43 + withTimeout(fetchLatestBlueskyPost(fetch), REQUEST_TIMEOUT), 44 + withTimeout(fetchRecentDocuments(5, fetch), REQUEST_TIMEOUT), 45 + withTimeout(fetchAllSupporters(), REQUEST_TIMEOUT), 46 + withTimeout(fetchRecentPopfeedReviews(fetch), REQUEST_TIMEOUT) 24 47 ]); 25 48 26 49 // Create page metadata with dynamic OG
+17 -12
vercel.json
··· 57 57 } 58 58 ], 59 59 "rewrites": [], 60 - "redirects": [ 61 - { 62 - "source": "/.well-known/nodeinfo", 63 - "destination": "https://ap.ewancroft.uk/.well-known/nodeinfo", 64 - "permanent": false 65 - }, 66 - { 67 - "source": "/.well-known/host-meta", 68 - "destination": "https://ap.ewancroft.uk/.well-known/host-meta", 69 - "permanent": false 70 - } 71 - ] 60 + "redirects": [ 61 + { 62 + "source": "/.well-known/nodeinfo", 63 + "destination": "https://ap.ewancroft.uk/.well-known/nodeinfo", 64 + "permanent": false 65 + }, 66 + { 67 + "source": "/.well-known/host-meta", 68 + "destination": "https://ap.ewancroft.uk/.well-known/host-meta", 69 + "permanent": false 70 + }, 71 + { 72 + "source": "/nodeinfo/2.0", 73 + "destination": "https://ap.ewancroft.uk/nodeinfo/2.0", 74 + "permanent": false 75 + } 76 + ] 72 77 }