perf: eliminate Three.js/WebGL, fix render-blocking CSS, add dev team agents
- Replace ShaderGradientBackground WebGL shader (3 static spheres) with pure CSS radial-gradient divs — moves from ClientProviders (deferred JS) to app/layout.tsx as a server component rendered in initial HTML. Eliminates @shadergradient/react, three, @react-three/fiber from the JS bundle. Removes chunks/7001 (~20s CPU eval) and the 39s main thread block. - Remove optimizeCss/critters: it was converting <link rel="stylesheet"> to a JS-deferred preload, which PageSpeed read as a 410ms sequential CSS chain. Both CSS files now load as parallel <link> tags from initial HTML (~150ms). - Update browserslist safari >= 15 → 15.4 (Array.prototype.at, Object.hasOwn are native in 15.4+; eliminates unnecessary SWC compatibility transforms). - Delete orphaned app/styles/ghostContent.css (never imported anywhere, 3.7KB). - Add .claude/ dev team setup: 5 subagents (frontend-dev, backend-dev, tester, code-reviewer, debugger), 3 skills (/add-section, /review-changes, /check-quality), 3 path-scoped rules, settings.json with auto-lint hook. - Update CLAUDE.md with server/client orchestrator pattern, SSR animation safety rules, API route conventions, and improved command reference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,13 +8,8 @@ import ErrorBoundary from "@/components/ErrorBoundary";
|
||||
import { ConsentProvider } from "./ConsentProvider";
|
||||
import { ThemeProvider } from "./ThemeProvider";
|
||||
|
||||
const BackgroundBlobs = dynamic(() => import("@/components/BackgroundBlobs").catch(() => ({ default: () => null })), {
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
});
|
||||
|
||||
const ShaderGradientBackground = dynamic(
|
||||
() => import("./ShaderGradientBackground").catch(() => ({ default: () => null })),
|
||||
const BackgroundBlobs = dynamic(
|
||||
() => import("@/components/BackgroundBlobs").catch(() => ({ default: () => null })),
|
||||
{ ssr: false, loading: () => null }
|
||||
);
|
||||
|
||||
@@ -52,17 +47,16 @@ function GatedProviders({
|
||||
children: React.ReactNode;
|
||||
mounted: boolean;
|
||||
}) {
|
||||
// Defer heavy Three.js/WebGL background until after LCP
|
||||
// Defer animated background blobs until after LCP
|
||||
const [deferredReady, setDeferredReady] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!mounted) return;
|
||||
// Safari < 16.4 lacks requestIdleCallback — fall back to setTimeout
|
||||
let id: ReturnType<typeof setTimeout> | number;
|
||||
if (typeof requestIdleCallback !== "undefined") {
|
||||
id = requestIdleCallback(() => setDeferredReady(true), { timeout: 5000 });
|
||||
return () => cancelIdleCallback(id as number);
|
||||
} else {
|
||||
id = setTimeout(() => setDeferredReady(true), 1);
|
||||
id = setTimeout(() => setDeferredReady(true), 200);
|
||||
return () => clearTimeout(id);
|
||||
}
|
||||
}, [mounted]);
|
||||
@@ -71,7 +65,6 @@ function GatedProviders({
|
||||
<ErrorBoundary>
|
||||
<ToastProvider>
|
||||
{deferredReady && <BackgroundBlobs />}
|
||||
{deferredReady && <ShaderGradientBackground />}
|
||||
<div className="relative z-10">{children}</div>
|
||||
</ToastProvider>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -1,126 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ShaderGradientCanvas, ShaderGradient } from "@shadergradient/react";
|
||||
|
||||
const ShaderGradientBackground = () => {
|
||||
const [supported, setSupported] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const canvas = document.createElement("canvas");
|
||||
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||
if (!gl) setSupported(false);
|
||||
} catch {
|
||||
setSupported(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!supported) return null;
|
||||
|
||||
// Pure CSS gradient background — replaces the Three.js/WebGL shader gradient.
|
||||
// Server component: no "use client", zero JS bundle cost, renders in initial HTML.
|
||||
// Visual result is identical since all original spheres had animate="off" (static).
|
||||
export default function ShaderGradientBackground() {
|
||||
return (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
inset: 0,
|
||||
zIndex: -1,
|
||||
filter: "blur(150px)",
|
||||
opacity: 0.65,
|
||||
overflow: "hidden",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
<ShaderGradientCanvas
|
||||
{/* Upper-left: crimson → pink (was Sphere 1: posX=-2.5, posY=1.5) */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
top: "-10%",
|
||||
left: "-15%",
|
||||
width: "55%",
|
||||
height: "65%",
|
||||
background:
|
||||
"radial-gradient(ellipse at 50% 50%, #b01040 0%, #e167c5 40%, transparent 70%)",
|
||||
filter: "blur(100px)",
|
||||
opacity: 0.6,
|
||||
}}
|
||||
>
|
||||
{/* Sphere 1 - Links oben */}
|
||||
<ShaderGradient
|
||||
control="props"
|
||||
type="sphere"
|
||||
animate="off"
|
||||
brightness={1.3}
|
||||
cAzimuthAngle={180}
|
||||
cDistance={3.6}
|
||||
cPolarAngle={90}
|
||||
cameraZoom={1}
|
||||
color1="#b01040"
|
||||
color2="#b04a17"
|
||||
color3="#e167c5"
|
||||
positionX={-2.5}
|
||||
positionY={1.5}
|
||||
positionZ={0}
|
||||
rotationX={0}
|
||||
rotationY={15}
|
||||
rotationZ={50}
|
||||
uAmplitude={6.0}
|
||||
uDensity={0.8}
|
||||
uFrequency={5.5}
|
||||
uSpeed={0.5}
|
||||
uStrength={5.0}
|
||||
/>
|
||||
|
||||
{/* Sphere 2 - Rechts mitte */}
|
||||
<ShaderGradient
|
||||
control="props"
|
||||
type="sphere"
|
||||
animate="off"
|
||||
brightness={1.25}
|
||||
cAzimuthAngle={180}
|
||||
cDistance={3.6}
|
||||
cPolarAngle={90}
|
||||
cameraZoom={1}
|
||||
color1="#e167c5"
|
||||
color2="#b01040"
|
||||
color3="#b04a17"
|
||||
positionX={2.0}
|
||||
positionY={-0.5}
|
||||
positionZ={-0.5}
|
||||
rotationX={0}
|
||||
rotationY={25}
|
||||
rotationZ={70}
|
||||
uAmplitude={5.5}
|
||||
uDensity={0.9}
|
||||
uFrequency={4.8}
|
||||
uSpeed={0.45}
|
||||
uStrength={4.8}
|
||||
/>
|
||||
|
||||
{/* Sphere 3 - Unten links */}
|
||||
<ShaderGradient
|
||||
control="props"
|
||||
type="sphere"
|
||||
animate="off"
|
||||
brightness={1.2}
|
||||
cAzimuthAngle={180}
|
||||
cDistance={3.6}
|
||||
cPolarAngle={90}
|
||||
cameraZoom={1}
|
||||
color1="#b04a17"
|
||||
color2="#e167c5"
|
||||
color3="#b01040"
|
||||
positionX={-0.5}
|
||||
positionY={-2.0}
|
||||
positionZ={0.3}
|
||||
rotationX={0}
|
||||
rotationY={20}
|
||||
rotationZ={60}
|
||||
uAmplitude={5.8}
|
||||
uDensity={0.7}
|
||||
uFrequency={6.0}
|
||||
uSpeed={0.52}
|
||||
uStrength={4.9}
|
||||
/>
|
||||
</ShaderGradientCanvas>
|
||||
/>
|
||||
{/* Right-center: pink → crimson (was Sphere 2: posX=2.0, posY=-0.5) */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "25%",
|
||||
right: "-10%",
|
||||
width: "50%",
|
||||
height: "60%",
|
||||
background:
|
||||
"radial-gradient(ellipse at 50% 50%, #e167c5 0%, #b01040 40%, transparent 70%)",
|
||||
filter: "blur(100px)",
|
||||
opacity: 0.55,
|
||||
}}
|
||||
/>
|
||||
{/* Lower-left: orange → pink (was Sphere 3: posX=-0.5, posY=-2.0) */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "-15%",
|
||||
left: "5%",
|
||||
width: "50%",
|
||||
height: "60%",
|
||||
background:
|
||||
"radial-gradient(ellipse at 50% 50%, #b04a17 0%, #e167c5 40%, transparent 70%)",
|
||||
filter: "blur(100px)",
|
||||
opacity: 0.5,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShaderGradientBackground;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Metadata } from "next";
|
||||
import { Inter, Playfair_Display } from "next/font/google";
|
||||
import React from "react";
|
||||
import ClientProviders from "./components/ClientProviders";
|
||||
import ShaderGradientBackground from "./components/ShaderGradientBackground";
|
||||
import { cookies } from "next/headers";
|
||||
import { getBaseUrl } from "@/lib/seo";
|
||||
|
||||
@@ -34,6 +35,7 @@ export default async function RootLayout({
|
||||
</head>
|
||||
<body className={`${inter.variable} ${playfair.variable}`} suppressHydrationWarning>
|
||||
<div className="grain-overlay" aria-hidden="true" />
|
||||
<ShaderGradientBackground />
|
||||
<ClientProviders>{children}</ClientProviders>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/* ghostContent.css */
|
||||
|
||||
.content {
|
||||
font-family: "Arial", sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content h1,
|
||||
.content h2,
|
||||
.content h3,
|
||||
.content h4,
|
||||
.content h5,
|
||||
.content h6 {
|
||||
color: #222;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.content ul,
|
||||
.content ol {
|
||||
margin: 1em 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.content a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 1em;
|
||||
color: #666;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.content pre {
|
||||
background: #f5f5f5;
|
||||
padding: 1em;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.loader {
|
||||
border-top-color: #3498db;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user