From 6b5e7422eb99b702da5aa9e78ea305234920090d Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sat, 13 Jun 2026 13:44:05 +0100 Subject: [PATCH] perf: prerender skeleton loader on dashboard page --- app/middleware/authenticated.ts | 10 +--- app/pages/dashboard.vue | 94 +++++++++++++++++++++++++++++++-- nuxt.config.ts | 2 +- 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/app/middleware/authenticated.ts b/app/middleware/authenticated.ts index 5233311..48bb7ea 100644 --- a/app/middleware/authenticated.ts +++ b/app/middleware/authenticated.ts @@ -1,14 +1,8 @@ export default defineNuxtRouteMiddleware(async () => { - // On SSR we must forward the request cookies so the whoami probe sees the - // session. On the client, same-origin `$fetch` already sends cookies. - const headers: Record = {} - if (import.meta.server) { - const cookie = useRequestHeader('cookie') - if (cookie) headers.cookie = cookie - } + if (import.meta.server) return try { - await $fetch('/api/me/whoami', { headers }) + await $fetch('/api/me/whoami') } catch (err: unknown) { const status = err && typeof err === 'object' diff --git a/app/pages/dashboard.vue b/app/pages/dashboard.vue index 5367be4..06ceed7 100644 --- a/app/pages/dashboard.vue +++ b/app/pages/dashboard.vue @@ -14,8 +14,14 @@ useSeoMeta({ description: 'Your synchub.to mirror status.', }) -const { data, refresh, error } = await useFetch('/api/me/dashboard', { server: false }) -const { data: accountsData, refresh: refreshAccounts } = await useFetch('/api/me/accounts', { server: false }) +const { data, refresh, error } = useFetch('/api/me/dashboard', { + server: false, + lazy: true, +}) +const { data: accountsData, refresh: refreshAccounts } = useFetch('/api/me/accounts', { + server: false, + lazy: true, +}) const switcher = useTemplateRef('switcher') const switcherOpen = ref(false) @@ -250,7 +256,31 @@ function fmtDate(iso: string | null): string { failed to load dashboard: {{ error.message }} -
+ + +

identity

@@ -557,6 +587,64 @@ h2 { color: var(--color-error); } +/* Skeleton: dimensions mirror the loaded cards so swapping in real data + doesn't shift layout. */ +.sk { + border-radius: var(--radius-sm); + background: var(--color-paper-3); + position: relative; + overflow: hidden; +} + +.sk::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient(90deg, transparent, color-mix(in oklch, var(--color-paper-2) 60%, transparent), transparent); + animation: sk-shimmer 1.4s var(--ease-in-out) infinite; +} + +@keyframes sk-shimmer { + to { transform: translateX(100%); } +} + +.sk--line { + height: 1.1em; + margin-block: var(--space-xs); +} + +.sk--meta { + height: 0.85em; +} + +.sk--block { + height: 3.2em; + margin-block: var(--space-sm); +} + +.sk--btn { + height: 2.1em; + width: 8rem; +} + +.sk-repo { + display: flex; + align-items: start; + justify-content: space-between; + gap: var(--space-lg); + padding-block: var(--space-md); + border-top: var(--rule-hair) solid var(--color-rule); +} + +.sk-repo:first-of-type { border-top: 0; } +.sk-repo__main { flex: 1; min-width: 0; } +.sk-repo .sk--btn { flex: none; width: 5.5rem; } + +@media (prefers-reduced-motion: reduce) { + .sk::after { animation: none; } +} + dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); diff --git a/nuxt.config.ts b/nuxt.config.ts index 9029424..32f169b 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -67,7 +67,7 @@ export default defineNuxtConfig({ }, routeRules: { '/': { noScripts: true, prerender: true }, - '/dashboard': { ssr: false, prerender: true }, + '/dashboard': { prerender: true }, '/connect': { noScripts: true }, }, nitro: { -- 2.51.2