Skip to content

Add loading.tsx skeletons for async marketing routes (docs, blog, changelog, free) #1243

@MODSetter

Description

@MODSetter

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions