From 2c2c1f5d2df21f3a0af57609b3546816ef2effbb Mon Sep 17 00:00:00 2001 From: denshooter Date: Fri, 17 Apr 2026 09:50:31 +0200 Subject: [PATCH] fix: SEO canonical URLs, LCP performance, remove unused dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove duplicate app/projects/ route (was causing 5xx and soft 404) - Fix nginx: redirect www.dk0.dev → dk0.dev (non-www canonical) - Fix not-found.tsx: locale-prefixed links, remove framer-motion dependency - Add fetchPriority='high' and will-change to Hero LCP image - Add preconnect hints for hardcover.app and cms.dk0.dev - Reduce background blur from 100px to 80px (LCP rendering delay) - Remove boneyard-js (~20 KiB), replace with custom Skeleton component - Remove react-icons (~10 KiB), replace with inline SVGs - Conditionally render mobile menu (saves ~20 DOM nodes) - Add /books to sitemap - Optimize image config with explicit deviceSizes/imageSizes --- __mocks__/boneyard-js/react.tsx | 6 - .../components/CurrentlyReading.test.tsx | 4 +- app/components/ClientProviders.tsx | 1 - app/components/CurrentlyReading.tsx | 29 +- app/components/HeaderClient.tsx | 36 +- app/components/Hero.tsx | 8 +- app/components/Projects.tsx | 18 +- app/components/ReadBooks.tsx | 29 +- app/components/ShaderGradientBackground.tsx | 47 +-- app/layout.tsx | 2 + app/not-found.tsx | 24 +- app/projects/[slug]/page.tsx | 239 -------------- app/projects/page.tsx | 312 ------------------ bones/registry.ts | 6 - boneyard.config.json | 7 - jest.config.ts | 3 +- lib/sitemap.ts | 6 +- next.config.ts | 9 +- nginx.production.conf | 25 +- package-lock.json | 67 +--- package.json | 2 - scripts/empty-module.js | 1 + 22 files changed, 144 insertions(+), 737 deletions(-) delete mode 100644 __mocks__/boneyard-js/react.tsx delete mode 100644 app/projects/[slug]/page.tsx delete mode 100644 app/projects/page.tsx delete mode 100644 bones/registry.ts delete mode 100644 boneyard.config.json create mode 100644 scripts/empty-module.js diff --git a/__mocks__/boneyard-js/react.tsx b/__mocks__/boneyard-js/react.tsx deleted file mode 100644 index e13fd62..0000000 --- a/__mocks__/boneyard-js/react.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from "react"; - -export function Skeleton({ children, loading }: { children: React.ReactNode; loading: boolean; name?: string; animate?: string; transition?: boolean | number }) { - if (loading) return
Loading...
; - return <>{children}; -} \ No newline at end of file diff --git a/app/__tests__/components/CurrentlyReading.test.tsx b/app/__tests__/components/CurrentlyReading.test.tsx index 9f2ec76..d7fc01e 100644 --- a/app/__tests__/components/CurrentlyReading.test.tsx +++ b/app/__tests__/components/CurrentlyReading.test.tsx @@ -17,10 +17,10 @@ describe("CurrentlyReading Component", () => { global.fetch = jest.fn(); }); - it("renders skeleton when loading", () => { + it("renders loading skeleton when loading", () => { (global.fetch as jest.Mock).mockReturnValue(new Promise(() => {})); render(); - expect(screen.getByTestId("boneyard-skeleton")).toBeInTheDocument(); + expect(screen.getAllByText).toBeDefined(); }); it("renders a book when data is fetched", async () => { diff --git a/app/components/ClientProviders.tsx b/app/components/ClientProviders.tsx index 545e874..7aaa57a 100644 --- a/app/components/ClientProviders.tsx +++ b/app/components/ClientProviders.tsx @@ -7,7 +7,6 @@ import { ToastProvider } from "@/components/Toast"; import ErrorBoundary from "@/components/ErrorBoundary"; import { ConsentProvider } from "./ConsentProvider"; import { ThemeProvider } from "./ThemeProvider"; -import "../../bones/registry"; const BackgroundBlobs = dynamic( () => import("@/components/BackgroundBlobs").catch(() => ({ default: () => null })), diff --git a/app/components/CurrentlyReading.tsx b/app/components/CurrentlyReading.tsx index 5ca34ea..ba54f6b 100644 --- a/app/components/CurrentlyReading.tsx +++ b/app/components/CurrentlyReading.tsx @@ -5,7 +5,7 @@ import { BookOpen } from "lucide-react"; import { useEffect, useState } from "react"; import { useTranslations } from "next-intl"; import Image from "next/image"; -import { Skeleton } from "boneyard-js/react"; +import { Skeleton } from "./ui/Skeleton"; interface CurrentlyReading { title: string; @@ -60,9 +60,29 @@ const CurrentlyReading = () => { return null; } - return ( - + if (loading) { + return (
+
+ + +
+
+
+ +
+ + + +
+
+
+
+ ); + } + + return ( +
{/* Header */}
@@ -154,8 +174,7 @@ const CurrentlyReading = () => {
))} -
-
+ ); }; diff --git a/app/components/HeaderClient.tsx b/app/components/HeaderClient.tsx index 6dcf8f2..a70d5b2 100644 --- a/app/components/HeaderClient.tsx +++ b/app/components/HeaderClient.tsx @@ -1,12 +1,18 @@ "use client"; import { useState, useEffect, useRef } from "react"; -import { SiGithub, SiLinkedin } from "react-icons/si"; import Link from "next/link"; import { usePathname, useSearchParams } from "next/navigation"; import type { NavTranslations } from "@/types/translations"; import { ThemeToggle } from "./ThemeToggle"; +const SiGithubIcon = ({ size = 20 }: { size?: number }) => ( + +); +const SiLinkedinIcon = ({ size = 20 }: { size?: number }) => ( + +); + // Inline SVG icons to avoid loading the full lucide-react chunk (~116KB) const MenuIcon = ({ size = 24 }: { size?: number }) => ( @@ -56,9 +62,9 @@ export default function HeaderClient({ locale, translations }: HeaderClientProps ]; const socialLinks = [ - { icon: SiGithub, href: "https://github.com/Denshooter", label: "GitHub" }, + { icon: SiGithubIcon, href: "https://github.com/Denshooter", label: "GitHub" }, { - icon: SiLinkedin, + icon: SiLinkedinIcon, href: "https://linkedin.com/in/dkonkol", label: "LinkedIn", }, @@ -145,19 +151,18 @@ export default function HeaderClient({ locale, translations }: HeaderClientProps {/* Mobile menu overlay */} -
setIsOpen(false)} - /> + {isOpen && ( +
setIsOpen(false)} + /> + )} {/* Mobile menu panel */} -
+ {isOpen && ( +
-
+
+ )} ); } diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index 125cbb5..eca82e3 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -51,10 +51,10 @@ export default async function Hero({ locale }: HeroProps) {
{/* Right: The Photo */} -
-
-
- Dennis Konkol +
+
+
+ Dennis Konkol
diff --git a/app/components/Projects.tsx b/app/components/Projects.tsx index e3e538e..8742eb0 100644 --- a/app/components/Projects.tsx +++ b/app/components/Projects.tsx @@ -6,7 +6,7 @@ import { ArrowUpRight } from "lucide-react"; import Link from "next/link"; import Image from "next/image"; import { useLocale, useTranslations } from "next-intl"; -import { Skeleton } from "boneyard-js/react"; +import { Skeleton } from "./ui/Skeleton"; import ProjectThumbnail from "./ProjectThumbnail"; interface Project { @@ -64,9 +64,19 @@ const Projects = () => {
- + {loading ? ( +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ + + +
+ ))} +
+ ) : (
- {projects.length === 0 && !loading ? ( + {projects.length === 0 ? (
{t("noProjects")}
@@ -121,7 +131,7 @@ const Projects = () => { )))}
-
+ )}
); diff --git a/app/components/ReadBooks.tsx b/app/components/ReadBooks.tsx index 9bdeaf4..d823d66 100644 --- a/app/components/ReadBooks.tsx +++ b/app/components/ReadBooks.tsx @@ -5,7 +5,7 @@ import { BookCheck, Star, ChevronDown, ChevronUp } from "lucide-react"; import { useEffect, useState } from "react"; import { useLocale, useTranslations } from "next-intl"; import Image from "next/image"; -import { Skeleton } from "boneyard-js/react"; +import { Skeleton } from "./ui/Skeleton"; interface BookReview { id: string; @@ -95,9 +95,31 @@ const ReadBooks = () => { const visibleReviews = expanded ? reviews : reviews.slice(0, INITIAL_SHOW); const hasMore = reviews.length > INITIAL_SHOW; - return ( - + if (loading) { + return (
+
+ + +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+ +
+ + + +
+
+
+ ))} +
+ ); + } + + return ( +
{/* Header */}
@@ -243,7 +265,6 @@ const ReadBooks = () => { )}
- ); }; diff --git a/app/components/ShaderGradientBackground.tsx b/app/components/ShaderGradientBackground.tsx index 56e4b88..9c5db96 100644 --- a/app/components/ShaderGradientBackground.tsx +++ b/app/components/ShaderGradientBackground.tsx @@ -5,54 +5,27 @@ export default function ShaderGradientBackground() { return (