my website at ewancroft.uk
6
fork

Configure Feed

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

feat(error): improve error page with contextual suggestions

- Add lucide icons for each error type (404, 403, 500, 503)
- Contextual suggestions based on error type
- Better action buttons with icons (Home, Try Again)
- Quick links for 404 pages (Archive, GitHub, About)
- Improved error message display for debugging
- Fixed Svelte 5 compatibility (removed deprecated svelte:component)

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>

+187 -37
+187 -37
src/routes/+error.svelte
··· 1 1 <script lang="ts"> 2 2 import { page } from '$app/stores'; 3 + import { MetaTags } from '$lib/components/seo'; 3 4 import { Card } from '$lib/components/ui'; 5 + import { Home, RefreshCw, FileQuestion, Shield, ServerCrash, AlertTriangle } from '@lucide/svelte'; 4 6 5 7 // Get error details from page store 6 8 const status = $derived($page.status); 7 9 const errorMessage = $derived($page.error?.message || 'An unexpected error occurred'); 10 + const pathname = $derived($page.url.pathname); 8 11 9 - // Error titles and descriptions based on status code 10 - const errorDetails = $derived.by(() => { 12 + // Error configurations with icon, title, description, and suggestions 13 + const errorConfig = $derived.by(() => { 11 14 switch (status) { 12 15 case 404: 13 16 return { 17 + icon: FileQuestion, 14 18 title: 'Page Not Found', 15 - description: `The page <code class="rounded bg-canvas-200 px-2 py-1 font-mono text-sm dark:bg-canvas-800">${$page.url.pathname}</code> could not be found.` 19 + description: `The page at <code class="rounded bg-canvas-200 px-2 py-1 font-mono text-sm dark:bg-canvas-800">${pathname}</code> doesn't exist or may have been moved.`, 20 + suggestions: [ 21 + 'Check the URL for typos', 22 + 'The page may have been renamed or deleted', 23 + 'Try navigating from the homepage' 24 + ], 25 + primaryAction: { label: 'Go to Homepage', href: '/', icon: Home }, 26 + secondaryAction: null 16 27 }; 17 28 case 403: 18 29 return { 19 - title: 'Access Forbidden', 20 - description: 'You do not have permission to access this resource.' 30 + icon: Shield, 31 + title: 'Access Denied', 32 + description: 'You don\'t have permission to access this resource. This could be due to authentication requirements or restricted access.', 33 + suggestions: [ 34 + 'Make sure you\'re logged in if required', 35 + 'The content may be private or restricted', 36 + 'Contact the site owner if you believe this is an error' 37 + ], 38 + primaryAction: { label: 'Go to Homepage', href: '/', icon: Home }, 39 + secondaryAction: null 21 40 }; 22 41 case 500: 23 42 return { 24 - title: 'Internal Server Error', 25 - description: 'Something went wrong on our end. Please try again later.' 43 + icon: ServerCrash, 44 + title: 'Something Went Wrong', 45 + description: 'An internal error occurred while processing your request. This is usually temporary.', 46 + suggestions: [ 47 + 'Try refreshing the page', 48 + 'Clear your browser cache', 49 + 'The issue has been logged and will be investigated' 50 + ], 51 + primaryAction: { label: 'Try Again', href: null, icon: RefreshCw, action: () => window.location.reload() }, 52 + secondaryAction: { label: 'Go to Homepage', href: '/', icon: Home } 26 53 }; 27 54 case 503: 28 55 return { 29 - title: 'Service Unavailable', 30 - description: 'The service is temporarily unavailable. Please try again in a moment.' 56 + icon: AlertTriangle, 57 + title: 'Service Temporarily Unavailable', 58 + description: 'The server is currently unavailable, usually due to maintenance or high load. Please try again shortly.', 59 + suggestions: [ 60 + 'Wait a few moments and try again', 61 + 'The site may be undergoing maintenance', 62 + 'Check back in a minute or two' 63 + ], 64 + primaryAction: { label: 'Try Again', href: null, icon: RefreshCw, action: () => window.location.reload() }, 65 + secondaryAction: { label: 'Go to Homepage', href: '/', icon: Home } 31 66 }; 32 67 default: 33 68 return { 69 + icon: AlertTriangle, 34 70 title: 'An Error Occurred', 35 - description: errorMessage 71 + description: `Something unexpected happened${pathname ? ` at <code class="rounded bg-canvas-200 px-2 py-1 font-mono text-sm dark:bg-canvas-800">${pathname}</code>` : ''}.`, 72 + suggestions: [ 73 + 'Try refreshing the page', 74 + 'Clear your browser cache', 75 + 'If the problem persists, please contact support' 76 + ], 77 + primaryAction: { label: 'Go to Homepage', href: '/', icon: Home }, 78 + secondaryAction: { label: 'Try Again', href: null, icon: RefreshCw, action: () => window.location.reload() } 36 79 }; 37 80 } 38 81 }); 82 + 83 + const meta = $derived({ 84 + title: `${status} - ${errorConfig.title}` 85 + }); 39 86 </script> 40 87 41 88 <svelte:head> 42 - <title>{status} - {errorDetails.title}</title> 89 + <title>{meta.title}</title> 90 + <meta name="description" content={errorConfig.description.replace(/<[^>]*>/g, '')} /> 43 91 </svelte:head> 44 92 45 - <div class="mx-auto max-w-2xl"> 93 + <MetaTags {meta} siteMeta={meta} /> 94 + 95 + <div class="mx-auto max-w-2xl py-8"> 46 96 <Card variant="elevated" padding="lg"> 47 97 {#snippet children()} 48 98 <div class="text-center"> 49 - <!-- Large status code number --> 50 - <div class="mb-6"> 51 - <h1 class="text-8xl font-bold text-primary-500 md:text-9xl dark:text-primary-400"> 99 + <!-- Icon with status code --> 100 + <div class="mb-6 flex flex-col items-center"> 101 + <div class="mb-4 rounded-full bg-primary-100 p-6 text-primary-600 dark:bg-primary-900/30 dark:text-primary-400"> 102 + {#if errorConfig.icon === FileQuestion} 103 + <FileQuestion class="h-16 w-16" /> 104 + {:else if errorConfig.icon === Shield} 105 + <Shield class="h-16 w-16" /> 106 + {:else if errorConfig.icon === ServerCrash} 107 + <ServerCrash class="h-16 w-16" /> 108 + {:else} 109 + <AlertTriangle class="h-16 w-16" /> 110 + {/if} 111 + </div> 112 + <h1 class="text-6xl font-bold text-ink-900 md:text-7xl dark:text-ink-50"> 52 113 {status} 53 114 </h1> 54 115 </div> 55 116 56 117 <!-- Error title --> 57 118 <h2 class="mb-4 text-2xl font-bold text-ink-900 md:text-3xl dark:text-ink-50"> 58 - {errorDetails.title} 119 + {errorConfig.title} 59 120 </h2> 60 121 61 122 <!-- Error description --> 62 123 <p class="mb-6 text-ink-700 dark:text-ink-200"> 63 - {@html errorDetails.description} 124 + {@html errorConfig.description} 64 125 </p> 65 126 66 - <!-- Show additional error message if it's different from the description --> 67 - {#if errorMessage && errorMessage !== errorDetails.description && status !== 404} 68 - <p 69 - class="mb-6 rounded-lg bg-canvas-200 p-4 text-sm text-ink-600 dark:bg-canvas-800 dark:text-ink-300" 70 - > 71 - {errorMessage} 72 - </p> 127 + <!-- Show additional error message if available and meaningful --> 128 + {#if errorMessage && !errorMessage.includes('Internal Error') && status !== 404} 129 + <div class="mb-6 rounded-lg bg-red-50 p-4 text-left dark:bg-red-900/20"> 130 + <p class="text-sm font-medium text-red-800 dark:text-red-200"> 131 + Error details: 132 + </p> 133 + <p class="mt-1 font-mono text-sm text-red-700 dark:text-red-300"> 134 + {errorMessage} 135 + </p> 136 + </div> 137 + {/if} 138 + 139 + <!-- Suggestions --> 140 + {#if errorConfig.suggestions.length > 0} 141 + <div class="mb-8 text-left"> 142 + <p class="mb-3 text-sm font-medium text-ink-600 dark:text-ink-400"> 143 + What you can try: 144 + </p> 145 + <ul class="space-y-2"> 146 + {#each errorConfig.suggestions as suggestion} 147 + <li class="flex items-start gap-2 text-sm text-ink-700 dark:text-ink-300"> 148 + <span class="mt-0.5 text-primary-500 dark:text-primary-400">•</span> 149 + <span>{suggestion}</span> 150 + </li> 151 + {/each} 152 + </ul> 153 + </div> 73 154 {/if} 74 155 75 156 <!-- Action buttons --> 76 157 <div class="flex flex-col items-center justify-center gap-3 sm:flex-row"> 77 - <a 78 - href="/" 79 - class="inline-flex w-full items-center justify-center rounded-lg bg-primary-500 px-6 py-3 font-medium text-white transition-colors hover:bg-primary-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700" 80 - > 81 - Return to Home 82 - </a> 158 + {#if errorConfig.primaryAction} 159 + {#if errorConfig.primaryAction.href} 160 + <a 161 + href={errorConfig.primaryAction.href} 162 + class="inline-flex w-full items-center justify-center gap-2 rounded-lg bg-primary-500 px-6 py-3 font-medium text-white transition-colors hover:bg-primary-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700" 163 + > 164 + {#if errorConfig.primaryAction.icon === Home} 165 + <Home class="h-5 w-5" /> 166 + {:else if errorConfig.primaryAction.icon === RefreshCw} 167 + <RefreshCw class="h-5 w-5" /> 168 + {/if} 169 + {errorConfig.primaryAction.label} 170 + </a> 171 + {:else if errorConfig.primaryAction.action} 172 + <button 173 + onclick={errorConfig.primaryAction.action} 174 + class="inline-flex w-full items-center justify-center gap-2 rounded-lg bg-primary-500 px-6 py-3 font-medium text-white transition-colors hover:bg-primary-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700" 175 + > 176 + {#if errorConfig.primaryAction.icon === Home} 177 + <Home class="h-5 w-5" /> 178 + {:else if errorConfig.primaryAction.icon === RefreshCw} 179 + <RefreshCw class="h-5 w-5" /> 180 + {/if} 181 + {errorConfig.primaryAction.label} 182 + </button> 183 + {/if} 184 + {/if} 83 185 84 - {#if status !== 404} 85 - <button 86 - onclick={() => window.location.reload()} 87 - class="inline-flex w-full items-center justify-center rounded-lg bg-canvas-300 px-6 py-3 font-medium text-ink-900 transition-colors hover:bg-canvas-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-canvas-600 sm:w-auto dark:bg-canvas-700 dark:text-ink-50 dark:hover:bg-canvas-600" 88 - > 89 - Try Again 90 - </button> 186 + {#if errorConfig.secondaryAction} 187 + {#if errorConfig.secondaryAction.href} 188 + <a 189 + href={errorConfig.secondaryAction.href} 190 + class="inline-flex w-full items-center justify-center gap-2 rounded-lg bg-canvas-200 px-6 py-3 font-medium text-ink-900 transition-colors hover:bg-canvas-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-canvas-600 sm:w-auto dark:bg-canvas-700 dark:text-ink-50 dark:hover:bg-canvas-600" 191 + > 192 + {#if errorConfig.secondaryAction.icon === Home} 193 + <Home class="h-5 w-5" /> 194 + {:else if errorConfig.secondaryAction.icon === RefreshCw} 195 + <RefreshCw class="h-5 w-5" /> 196 + {/if} 197 + {errorConfig.secondaryAction.label} 198 + </a> 199 + {:else if errorConfig.secondaryAction.action} 200 + <button 201 + onclick={errorConfig.secondaryAction.action} 202 + class="inline-flex w-full items-center justify-center gap-2 rounded-lg bg-canvas-200 px-6 py-3 font-medium text-ink-900 transition-colors hover:bg-canvas-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-canvas-600 sm:w-auto dark:bg-canvas-700 dark:text-ink-50 dark:hover:bg-canvas-600" 203 + > 204 + {#if errorConfig.secondaryAction.icon === Home} 205 + <Home class="h-5 w-5" /> 206 + {:else if errorConfig.secondaryAction.icon === RefreshCw} 207 + <RefreshCw class="h-5 w-5" /> 208 + {/if} 209 + {errorConfig.secondaryAction.label} 210 + </button> 211 + {/if} 91 212 {/if} 92 213 </div> 214 + 215 + <!-- Helpful links for 404 --> 216 + {#if status === 404} 217 + <div class="mt-8 border-t border-canvas-200 pt-6 dark:border-canvas-800"> 218 + <p class="mb-3 text-sm font-medium text-ink-600 dark:text-ink-400"> 219 + Looking for something specific? 220 + </p> 221 + <div class="flex flex-wrap justify-center gap-2"> 222 + <a 223 + href="/archive" 224 + class="rounded-lg bg-canvas-100 px-4 py-2 text-sm text-ink-700 transition-colors hover:bg-canvas-200 dark:bg-canvas-800 dark:text-ink-300 dark:hover:bg-canvas-700" 225 + > 226 + Archive 227 + </a> 228 + <a 229 + href="/github" 230 + class="rounded-lg bg-canvas-100 px-4 py-2 text-sm text-ink-700 transition-colors hover:bg-canvas-200 dark:bg-canvas-800 dark:text-ink-300 dark:hover:bg-canvas-700" 231 + > 232 + GitHub 233 + </a> 234 + <a 235 + href="/site/meta" 236 + class="rounded-lg bg-canvas-100 px-4 py-2 text-sm text-ink-700 transition-colors hover:bg-canvas-200 dark:bg-canvas-800 dark:text-ink-300 dark:hover:bg-canvas-700" 237 + > 238 + About 239 + </a> 240 + </div> 241 + </div> 242 + {/if} 93 243 </div> 94 244 {/snippet} 95 245 </Card>