diff --git a/app/(app)/(auth)/layout.tsx b/app/(app)/(auth)/layout.tsx
index cc30d85f..7f098d46 100644
--- a/app/(app)/(auth)/layout.tsx
+++ b/app/(app)/(auth)/layout.tsx
@@ -14,7 +14,7 @@
* would just no-op).
*/
-import { useEffect } from "react";
+import { Suspense, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useAuth } from "@/components/auth/auth-provider";
@@ -23,6 +23,24 @@ export default function AuthLayout({
}: {
children: React.ReactNode;
}) {
+ // useSearchParams forces a CSR bailout during prerender, so Next.js
+ // requires a Suspense boundary anywhere it's read. The gate is the
+ // only consumer here — isolate it so the rest of the layout can still
+ // SSR normally.
+ return (
+
+ {children}}>
+ {children}
+
+
+ );
+}
+
+function AuthShell({ children }: { children: React.ReactNode }) {
+ return
{children}
;
+}
+
+function AuthGate({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
const params = useSearchParams();
@@ -35,16 +53,8 @@ export default function AuthLayout({
// Don't flash the form for an instant before the redirect fires.
if (!loading && user) {
- return (
-
- Redirecting…
-
- );
+ return Redirecting…
;
}
- return (
-
- {children}
-
- );
+ return {children};
}
diff --git a/components/social/collection-scroll-card.tsx b/components/social/collection-scroll-card.tsx
index 90d31d56..401df0d8 100644
--- a/components/social/collection-scroll-card.tsx
+++ b/components/social/collection-scroll-card.tsx
@@ -54,22 +54,23 @@ export function CollectionScrollCard({
}) {
const scrollRef = useRef(null);
const cardRef = useRef(null);
- const [posts, setPosts] = useState(initialPosts ?? []);
+ // `fetched` only holds posts pulled by the lazy IntersectionObserver
+ // fetch. When the parent supplies `initialPosts`, we derive `posts`
+ // from it directly — no mirror-into-state effect required, which means
+ // fresh data from the parent (e.g. after an edit) shows up on the very
+ // next render without a setState-cascade.
+ const [fetched, setFetched] = useState([]);
const [fetchState, setFetchState] = useState(
initialPosts ? "loaded" : "idle",
);
const [activeIndex, setActiveIndex] = useState(0);
- // Reconcile when the parent supplies fresh posts (e.g. after editing).
- useEffect(() => {
- if (!initialPosts) return;
- setPosts(initialPosts);
- setFetchState("loaded");
- }, [initialPosts]);
+ const posts = initialPosts ?? fetched;
// Fetch posts the first time the carousel enters the viewport (with a
// 200px rootMargin so we kick it off just before it's visible). Skipped
- // entirely when `initialPosts` was provided.
+ // entirely when `initialPosts` was provided (fetchState starts at
+ // "loaded" in that case so the early-return below fires).
useEffect(() => {
if (fetchState !== "idle") return;
const el = cardRef.current;
@@ -83,7 +84,7 @@ export function CollectionScrollCard({
collections
.get(c.id)
.then(({ posts: rows }) => {
- setPosts(rows ?? []);
+ setFetched(rows ?? []);
setFetchState("loaded");
})
.catch(() => setFetchState("error"));