From f311d3c417c87d7a956b2ae0a5c509a1a7974afe Mon Sep 17 00:00:00 2001 From: gunshiz Date: Tue, 6 Oct 2026 15:15:39 +0700 Subject: [PATCH] fix(images) : retry failed CDN images through Cloudflare resizing --- app/admin/[character]/[page]/page.tsx | 2 +- app/admin/[character]/page.tsx | 2 +- app/donate/page.tsx | 2 +- app/thanks/page.tsx | 2 +- components/admin/admin-header.tsx | 2 +- components/admin/create-guide-flow.tsx | 2 +- components/admin/glossary-manager.tsx | 2 +- components/admin/guide-overview-form.tsx | 2 +- components/admin/guide-section-editor.tsx | 2 +- components/admin/guide-section-nav.tsx | 2 +- components/admin/overview-image-generator.tsx | 3 +- components/admin/structured-dashboard.tsx | 2 +- components/admin/stygian-editor.tsx | 2 +- components/admin/ui/catalog-select.tsx | 2 +- components/admin/ui/option-select.tsx | 2 +- components/commission/commission-intro.tsx | 2 +- components/commission/request-form.tsx | 2 +- components/commission/request-summary.tsx | 2 +- components/public/condition-switcher.tsx | 2 +- components/public/constellation-image.tsx | 2 +- components/public/element-icon.tsx | 2 +- components/public/guide-export-branding.tsx | 2 +- components/public/material-poster.tsx | 2 +- components/public/public-navigation.tsx | 2 +- components/public/site-header.tsx | 2 +- components/public/structured-directory.tsx | 2 +- components/public/structured-guide.tsx | 2 +- components/public/stygian-guide.tsx | 2 +- components/public/team-search.tsx | 2 +- components/public/weapon-image-export.tsx | 2 +- components/stygian/stygian-boss-display.tsx | 2 +- components/ui/resilient-image.tsx | 34 +++++++++++++++++++ 32 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 components/ui/resilient-image.tsx diff --git a/app/admin/[character]/[page]/page.tsx b/app/admin/[character]/[page]/page.tsx index dffba80..a60ffa8 100644 --- a/app/admin/[character]/[page]/page.tsx +++ b/app/admin/[character]/[page]/page.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { ArrowLeftIcon } from "lucide-react"; import { notFound, redirect } from "next/navigation"; import { connection } from "next/server"; diff --git a/app/admin/[character]/page.tsx b/app/admin/[character]/page.tsx index b9de28d..1847ed9 100644 --- a/app/admin/[character]/page.tsx +++ b/app/admin/[character]/page.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react"; import { notFound, redirect } from "next/navigation"; diff --git a/app/donate/page.tsx b/app/donate/page.tsx index 678803c..70ed0fa 100644 --- a/app/donate/page.tsx +++ b/app/donate/page.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { SiteFooter } from "@/components/public/site-footer"; import { SiteHeader } from "@/components/public/site-header"; diff --git a/app/thanks/page.tsx b/app/thanks/page.tsx index 1fb89e5..3a01847 100644 --- a/app/thanks/page.tsx +++ b/app/thanks/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { ExternalLinkIcon } from "lucide-react"; diff --git a/components/admin/admin-header.tsx b/components/admin/admin-header.tsx index a628df6..3745bfe 100644 --- a/components/admin/admin-header.tsx +++ b/components/admin/admin-header.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { diff --git a/components/admin/create-guide-flow.tsx b/components/admin/create-guide-flow.tsx index 5650d8a..993c851 100644 --- a/components/admin/create-guide-flow.tsx +++ b/components/admin/create-guide-flow.tsx @@ -2,7 +2,7 @@ import { searchItems } from "@/lib/search"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { useRouter } from "next/navigation"; import { ArrowLeftIcon, CheckIcon, SearchIcon } from "lucide-react"; import { useMemo, useState, useTransition } from "react"; diff --git a/components/admin/glossary-manager.tsx b/components/admin/glossary-manager.tsx index 67c2599..1df0166 100644 --- a/components/admin/glossary-manager.tsx +++ b/components/admin/glossary-manager.tsx @@ -2,7 +2,7 @@ import { searchItems } from "@/lib/search"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { SearchIcon, Trash2Icon } from "lucide-react"; import { useMemo, useState, useTransition } from "react"; import { useRouter } from "next/navigation"; diff --git a/components/admin/guide-overview-form.tsx b/components/admin/guide-overview-form.tsx index 4d21c5d..8e2e776 100644 --- a/components/admin/guide-overview-form.tsx +++ b/components/admin/guide-overview-form.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { closestCenter, DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent } from "@dnd-kit/core"; import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; diff --git a/components/admin/guide-section-editor.tsx b/components/admin/guide-section-editor.tsx index adbf900..7d13471 100644 --- a/components/admin/guide-section-editor.tsx +++ b/components/admin/guide-section-editor.tsx @@ -3,7 +3,7 @@ import { searchItems } from "@/lib/search"; import dynamic from "next/dynamic"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { useRouter } from "next/navigation"; import { ArrowLeftIcon, diff --git a/components/admin/guide-section-nav.tsx b/components/admin/guide-section-nav.tsx index 4c197cb..a177ddd 100644 --- a/components/admin/guide-section-nav.tsx +++ b/components/admin/guide-section-nav.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import type { MouseEvent } from "react"; diff --git a/components/admin/overview-image-generator.tsx b/components/admin/overview-image-generator.tsx index c698ef1..8f3b6d8 100644 --- a/components/admin/overview-image-generator.tsx +++ b/components/admin/overview-image-generator.tsx @@ -1,6 +1,7 @@ "use client"; -import Image, { getImageProps } from "next/image"; +import { getImageProps } from "next/image"; +import Image from "@/components/ui/resilient-image"; import { closestCenter, DndContext, diff --git a/components/admin/structured-dashboard.tsx b/components/admin/structured-dashboard.tsx index f0a5fd4..7cfea74 100644 --- a/components/admin/structured-dashboard.tsx +++ b/components/admin/structured-dashboard.tsx @@ -2,7 +2,7 @@ import { searchItems } from "@/lib/search"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { diff --git a/components/admin/stygian-editor.tsx b/components/admin/stygian-editor.tsx index ec8f946..0f112ff 100644 --- a/components/admin/stygian-editor.tsx +++ b/components/admin/stygian-editor.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useRef, useState, useTransition, type ReactNode } from "react"; diff --git a/components/admin/ui/catalog-select.tsx b/components/admin/ui/catalog-select.tsx index 50c26f0..076a515 100644 --- a/components/admin/ui/catalog-select.tsx +++ b/components/admin/ui/catalog-select.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { CircleHelpIcon } from "lucide-react"; import type { ReactNode } from "react"; diff --git a/components/admin/ui/option-select.tsx b/components/admin/ui/option-select.tsx index a1f8e36..2a23319 100644 --- a/components/admin/ui/option-select.tsx +++ b/components/admin/ui/option-select.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import type { ComponentProps, ReactNode } from "react"; import { diff --git a/components/commission/commission-intro.tsx b/components/commission/commission-intro.tsx index 0a9a96d..df9e88c 100644 --- a/components/commission/commission-intro.tsx +++ b/components/commission/commission-intro.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { UserRoundIcon } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; diff --git a/components/commission/request-form.tsx b/components/commission/request-form.tsx index 0c4915b..c9ae40c 100644 --- a/components/commission/request-form.tsx +++ b/components/commission/request-form.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { UserRoundIcon, XIcon } from "lucide-react"; import { toast } from "sonner"; import { ArtifactSelect, CharacterSelect, WeaponSelect } from "@/components/admin/ui/catalog-select"; diff --git a/components/commission/request-summary.tsx b/components/commission/request-summary.tsx index 216ff18..468a2c8 100644 --- a/components/commission/request-summary.tsx +++ b/components/commission/request-summary.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { ArrowUpRightIcon } from "lucide-react"; import type { ReactNode } from "react"; import { Badge } from "@/components/ui/badge"; diff --git a/components/public/condition-switcher.tsx b/components/public/condition-switcher.tsx index 9c44762..0ba773e 100644 --- a/components/public/condition-switcher.tsx +++ b/components/public/condition-switcher.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { MousePointerClickIcon } from "lucide-react"; import { Children, Fragment, type ReactNode, useState } from "react"; import { Separator } from "@/components/ui/separator"; diff --git a/components/public/constellation-image.tsx b/components/public/constellation-image.tsx index 291e5b9..10119d8 100644 --- a/components/public/constellation-image.tsx +++ b/components/public/constellation-image.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { GuideExportBranding } from "@/components/public/guide-export-branding"; import { ExportText } from "@/components/public/export-language"; diff --git a/components/public/element-icon.tsx b/components/public/element-icon.tsx index eea8058..c0967ec 100644 --- a/components/public/element-icon.tsx +++ b/components/public/element-icon.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { elementIconFilter } from "@/lib/catalog/element"; diff --git a/components/public/guide-export-branding.tsx b/components/public/guide-export-branding.tsx index b6e40a1..02a5344 100644 --- a/components/public/guide-export-branding.tsx +++ b/components/public/guide-export-branding.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; export function GuideExportBranding({ backgroundUrl, diff --git a/components/public/material-poster.tsx b/components/public/material-poster.tsx index b6e8543..d8f31ec 100644 --- a/components/public/material-poster.tsx +++ b/components/public/material-poster.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { useRef, type PointerEvent } from "react"; diff --git a/components/public/public-navigation.tsx b/components/public/public-navigation.tsx index d60e91b..62a4d3a 100644 --- a/components/public/public-navigation.tsx +++ b/components/public/public-navigation.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { usePathname } from "next/navigation"; import { BookOpenIcon, MenuIcon } from "lucide-react"; diff --git a/components/public/site-header.tsx b/components/public/site-header.tsx index 84c5481..5546156 100644 --- a/components/public/site-header.tsx +++ b/components/public/site-header.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { Suspense } from "react"; import { PencilIcon } from "lucide-react"; diff --git a/components/public/structured-directory.tsx b/components/public/structured-directory.tsx index d7629ba..1e5d109 100644 --- a/components/public/structured-directory.tsx +++ b/components/public/structured-directory.tsx @@ -2,7 +2,7 @@ import { searchItems } from "@/lib/search"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { SearchIcon, SlidersHorizontalIcon, StarIcon, UsersIcon, XIcon } from "lucide-react"; diff --git a/components/public/structured-guide.tsx b/components/public/structured-guide.tsx index c9ed54a..8a3f99d 100644 --- a/components/public/structured-guide.tsx +++ b/components/public/structured-guide.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import localFont from "next/font/local"; import Link from "next/link"; import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react"; diff --git a/components/public/stygian-guide.tsx b/components/public/stygian-guide.tsx index 8c1bea1..1e54d1c 100644 --- a/components/public/stygian-guide.tsx +++ b/components/public/stygian-guide.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; diff --git a/components/public/team-search.tsx b/components/public/team-search.tsx index 112abe0..98954bf 100644 --- a/components/public/team-search.tsx +++ b/components/public/team-search.tsx @@ -1,7 +1,7 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import Link from "next/link"; import { ArrowUpRightIcon, SearchIcon, XIcon } from "lucide-react"; import { Fragment, useMemo, useState, type ReactNode } from "react"; diff --git a/components/public/weapon-image-export.tsx b/components/public/weapon-image-export.tsx index 6dbd0c4..b9bd909 100644 --- a/components/public/weapon-image-export.tsx +++ b/components/public/weapon-image-export.tsx @@ -1,6 +1,6 @@ "use client"; -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { DownloadIcon } from "lucide-react"; import { useState, type ReactNode } from "react"; diff --git a/components/stygian/stygian-boss-display.tsx b/components/stygian/stygian-boss-display.tsx index b26db28..ac63256 100644 --- a/components/stygian/stygian-boss-display.tsx +++ b/components/stygian/stygian-boss-display.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "@/components/ui/resilient-image"; import { CheckIcon, XIcon } from "lucide-react"; import type { ReactNode } from "react"; diff --git a/components/ui/resilient-image.tsx b/components/ui/resilient-image.tsx new file mode 100644 index 0000000..a6c3ae9 --- /dev/null +++ b/components/ui/resilient-image.tsx @@ -0,0 +1,34 @@ +"use client"; + +import NextImage, { type ImageProps } from "next/image"; +import { useState } from "react"; + +const CDN_ORIGIN = "https://buzz-cdn.astrxl.dev"; + +export default function ResilientImage({ src, unoptimized, onError, ...props }: ImageProps) { + const [fallback, setFallback] = useState<{ source: string; url: string } | null>(null); + const fallbackUrl = typeof src === "string" && fallback?.source === src ? fallback.url : null; + + return ( + { + if (!fallbackUrl && typeof src === "string" && src.startsWith(`${CDN_ORIGIN}/`)) { + const source = new URL(src); + if (!source.pathname.startsWith("/cdn-cgi/image/")) { + const optimized = new URL(event.currentTarget.currentSrc); + const width = Number(optimized.searchParams.get("w")) || 1080; + const quality = Number(optimized.searchParams.get("q")) || 75; + setFallback({ + source: src, + url: `${CDN_ORIGIN}/cdn-cgi/image/width=${width},quality=${quality},format=auto${source.pathname}${source.search}`, + }); + } + } + onError?.(event); + }} + /> + ); +}