Description
Several public/marketing route segments have async server pages that do real work (MDX source resolution, fetch() calls) but no sibling loading.tsx. Users see a blank screen during navigation. Next.js App Router uses loading.tsx to stream an instant UI while server work finishes, which significantly improves perceived performance on slow networks / cold caches.
Missing loading.tsx for these routes
| Route |
Page file |
Why it's async |
/docs/* |
surfsense_web/app/docs/[[...slug]]/page.tsx |
Awaits params + getDocPage + MDX body |
/blog |
surfsense_web/app/(home)/blog/page.tsx |
Awaits source.getPages() |
/blog/[slug] |
surfsense_web/app/(home)/blog/[slug]/page.tsx |
Awaits params + MDX body |
/changelog |
surfsense_web/app/(home)/changelog/page.tsx |
Awaits source.getPages() |
/free |
surfsense_web/app/(home)/free/page.tsx |
Awaits getModels() fetch |
/free/[model_slug] |
surfsense_web/app/(home)/free/[model_slug]/page.tsx |
Awaits Promise.all([getModel, getAllModels]) |
For reference, app/dashboard/[search_space_id]/logs/loading.tsx and .../new-chat/loading.tsx already implement the right pattern.
What to do
Create a sibling loading.tsx in each missing route segment. Each file should:
- Be a Server Component (no
"use client")
- Export a default function returning a skeleton that matches the final page's layout (header, content area)
- Use Tailwind classes already in the project (e.g.
animate-pulse, bg-muted, bg-neutral-200 dark:bg-neutral-800)
- Keep file small (under ~60 lines)
Example — surfsense_web/app/(home)/blog/loading.tsx:
export default function BlogIndexLoading() {
return (
<main className="min-h-screen">
<div className="container mx-auto px-4 py-16">
<div className="mb-8 h-10 w-48 animate-pulse rounded-md bg-neutral-200 dark:bg-neutral-800" />
<div className="mb-12 h-12 w-full max-w-md animate-pulse rounded-full bg-neutral-200 dark:bg-neutral-800" />
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="space-y-3">
<div className="aspect-video w-full animate-pulse rounded-lg bg-neutral-200 dark:bg-neutral-800" />
<div className="h-5 w-3/4 animate-pulse rounded bg-neutral-200 dark:bg-neutral-800" />
<div className="h-4 w-full animate-pulse rounded bg-neutral-200 dark:bg-neutral-800" />
</div>
))}
</div>
</div>
</main>
);
}
Repeat the same pattern (tailored skeleton) for each route.
Acceptance criteria
- All 6
loading.tsx files exist in the specified segments
- Each returns a reasonable skeleton that matches its route's layout
- Visible by throttling the network in DevTools and navigating to each route
- No runtime errors;
next build succeeds
Notes for contributors
- A skeleton only needs to be approximate — visual continuity matters more than pixel-perfect matching
- Do not create a loading file for a route that already has one
- Keep every file a Server Component
Description
Several public/marketing route segments have
asyncserver pages that do real work (MDX source resolution,fetch()calls) but no siblingloading.tsx. Users see a blank screen during navigation. Next.js App Router usesloading.tsxto stream an instant UI while server work finishes, which significantly improves perceived performance on slow networks / cold caches.Missing
loading.tsxfor these routes/docs/*surfsense_web/app/docs/[[...slug]]/page.tsxgetDocPage+ MDX body/blogsurfsense_web/app/(home)/blog/page.tsxsource.getPages()/blog/[slug]surfsense_web/app/(home)/blog/[slug]/page.tsx/changelogsurfsense_web/app/(home)/changelog/page.tsxsource.getPages()/freesurfsense_web/app/(home)/free/page.tsxgetModels()fetch/free/[model_slug]surfsense_web/app/(home)/free/[model_slug]/page.tsxPromise.all([getModel, getAllModels])For reference,
app/dashboard/[search_space_id]/logs/loading.tsxand.../new-chat/loading.tsxalready implement the right pattern.What to do
Create a sibling
loading.tsxin each missing route segment. Each file should:"use client")animate-pulse,bg-muted,bg-neutral-200 dark:bg-neutral-800)Example —
surfsense_web/app/(home)/blog/loading.tsx:Repeat the same pattern (tailored skeleton) for each route.
Acceptance criteria
loading.tsxfiles exist in the specified segmentsnext buildsucceedsNotes for contributors