diff --git a/src/app/changelog/feedSkeleton.tsx b/src/app/changelog/feedSkeleton.tsx new file mode 100644 index 0000000..10a7ab9 --- /dev/null +++ b/src/app/changelog/feedSkeleton.tsx @@ -0,0 +1,40 @@ +import { LoadingArticle } from "@/client/components/article"; + +// Shared skeleton for the changelog feed. Used both by the route-level +// loading.tsx and as the fallback on the index page so the +// static shell can be prerendered while the dynamic feed streams in. +export function FeedSkeleton() { + return ( +
+
+
+ {/* Feed skeleton */} +
+ {/* Month divider skeleton */} +
+
+
+
+ + + +
+ + {/* Sidebar skeleton — desktop only */} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/app/changelog/loading.tsx b/src/app/changelog/loading.tsx index 5dab378..b47a6fa 100644 --- a/src/app/changelog/loading.tsx +++ b/src/app/changelog/loading.tsx @@ -1,43 +1,12 @@ import { Fragment } from "react"; - -import { LoadingArticle } from "@/client/components/article"; +import { FeedSkeleton } from "./feedSkeleton"; import Header from "./header"; export default function Loading() { return (
-
-
-
- {/* Feed skeleton */} -
- {/* Month divider skeleton */} -
-
-
-
- - - -
- - {/* Sidebar skeleton — desktop only */} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ ); } diff --git a/src/app/changelog/page.tsx b/src/app/changelog/page.tsx index ffef3e5..e86ac50 100644 --- a/src/app/changelog/page.tsx +++ b/src/app/changelog/page.tsx @@ -1,22 +1,35 @@ import type { Metadata } from "next"; import { connection } from "next/server"; -import { Fragment } from "react"; +import { Fragment, Suspense } from "react"; import { ChangelogList } from "@/client/components/list"; import { getChangelogSummaries } from "../../server/utils"; +import { FeedSkeleton } from "./feedSkeleton"; import Header from "./header"; -export default async function Page() { - await connection(); - const changelogs = await getChangelogSummaries(); - +export default function Page() { + // Keep the header + document metadata in a statically-prerenderable shell and + // stream the dynamic, search-param-driven feed inside an explicit Suspense + // boundary. Reading URL state (via nuqs/useSearchParams in ) + // outside of a Suspense boundary makes the prerendered tree disagree with the + // client resume under `cacheComponents`, which surfaced as a hydration + // mismatch ("Cannot read properties of null (reading 'parentNode')"). return (
- + }> + + ); } +async function ChangelogFeed() { + await connection(); + const changelogs = await getChangelogSummaries(); + + return ; +} + export function generateMetadata(): Metadata { return { description: