Fix: eliminate reload-only hydration mismatches on home
Make HomePage a server component and mount ActivityFeed via a client-only wrapper to avoid Suspense/dynamic boundary differences between SSR and hydration.
This commit is contained in:
31
app/_ui/ActivityFeedClient.tsx
Normal file
31
app/_ui/ActivityFeedClient.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type ActivityFeedComponent = React.ComponentType<Record<string, never>>;
|
||||||
|
|
||||||
|
export default function ActivityFeedClient() {
|
||||||
|
const [Comp, setComp] = useState<ActivityFeedComponent | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const mod = await import("../components/ActivityFeed");
|
||||||
|
const C = (mod as unknown as { default?: ActivityFeedComponent }).default;
|
||||||
|
if (!cancelled && typeof C === "function") {
|
||||||
|
setComp(() => C);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!Comp) return null;
|
||||||
|
return <Comp />;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Header from "../components/Header";
|
import Header from "../components/Header";
|
||||||
import Hero from "../components/Hero";
|
import Hero from "../components/Hero";
|
||||||
import About from "../components/About";
|
import About from "../components/About";
|
||||||
@@ -7,19 +5,7 @@ import Projects from "../components/Projects";
|
|||||||
import Contact from "../components/Contact";
|
import Contact from "../components/Contact";
|
||||||
import Footer from "../components/Footer";
|
import Footer from "../components/Footer";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import dynamic from "next/dynamic";
|
import ActivityFeedClient from "./ActivityFeedClient";
|
||||||
import ErrorBoundary from "@/components/ErrorBoundary";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
// Wrap ActivityFeed in error boundary to prevent crashes
|
|
||||||
const ActivityFeed = dynamic(
|
|
||||||
() =>
|
|
||||||
import("../components/ActivityFeed").catch(() => ({ default: () => null })),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => null,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
@@ -46,9 +32,7 @@ export default function HomePage() {
|
|||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ErrorBoundary>
|
<ActivityFeedClient />
|
||||||
<ActivityFeed />
|
|
||||||
</ErrorBoundary>
|
|
||||||
<Header />
|
<Header />
|
||||||
{/* Spacer to prevent navbar overlap */}
|
{/* Spacer to prevent navbar overlap */}
|
||||||
<div className="h-24 md:h-32" aria-hidden="true"></div>
|
<div className="h-24 md:h-32" aria-hidden="true"></div>
|
||||||
@@ -62,14 +46,9 @@ export default function HomePage() {
|
|||||||
viewBox="0 0 1440 120"
|
viewBox="0 0 1440 120"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
>
|
>
|
||||||
<motion.path
|
<path
|
||||||
d="M0,64 C240,96 480,32 720,64 C960,96 1200,32 1440,64 L1440,120 L0,120 Z"
|
d="M0,64 C240,96 480,32 720,64 C960,96 1200,32 1440,64 L1440,120 L0,120 Z"
|
||||||
fill="url(#gradient1)"
|
fill="url(#gradient1)"
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{
|
|
||||||
opacity: { duration: 0.8, delay: 0.3 },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
@@ -90,14 +69,9 @@ export default function HomePage() {
|
|||||||
viewBox="0 0 1440 120"
|
viewBox="0 0 1440 120"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
>
|
>
|
||||||
<motion.path
|
<path
|
||||||
d="M0,32 C240,64 480,96 720,32 C960,64 1200,96 1440,32 L1440,120 L0,120 Z"
|
d="M0,32 C240,64 480,96 720,32 C960,64 1200,96 1440,32 L1440,120 L0,120 Z"
|
||||||
fill="url(#gradient2)"
|
fill="url(#gradient2)"
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{
|
|
||||||
opacity: { duration: 0.8, delay: 0.3 },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
@@ -118,14 +92,9 @@ export default function HomePage() {
|
|||||||
viewBox="0 0 1440 120"
|
viewBox="0 0 1440 120"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
>
|
>
|
||||||
<motion.path
|
<path
|
||||||
d="M0,96 C240,32 480,64 720,96 C960,32 1200,64 1440,96 L1440,120 L0,120 Z"
|
d="M0,96 C240,32 480,64 720,96 C960,32 1200,64 1440,96 L1440,120 L0,120 Z"
|
||||||
fill="url(#gradient3)"
|
fill="url(#gradient3)"
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{
|
|
||||||
opacity: { duration: 0.8, delay: 0.3 },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="gradient3" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="gradient3" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
|||||||
Reference in New Issue
Block a user