fix(images) : retry failed CDN images through Cloudflare resizing
CI / Build immutable images and deploy (push) Canceled after 0s
CI / Verify (push) Canceled after 53s

This commit is contained in:
2026-10-06 15:15:39 +07:00 Unverified
parent 445865bf42
commit f311d3c417
32 changed files with 66 additions and 31 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { ArrowLeftIcon } from "lucide-react"; import { ArrowLeftIcon } from "lucide-react";
import { notFound, redirect } from "next/navigation"; import { notFound, redirect } from "next/navigation";
import { connection } from "next/server"; import { connection } from "next/server";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react"; import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react";
import { notFound, redirect } from "next/navigation"; import { notFound, redirect } from "next/navigation";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { SiteFooter } from "@/components/public/site-footer"; import { SiteFooter } from "@/components/public/site-footer";
import { SiteHeader } from "@/components/public/site-header"; import { SiteHeader } from "@/components/public/site-header";
+1 -1
View File
@@ -1,5 +1,5 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { import {
+1 -1
View File
@@ -2,7 +2,7 @@
import { searchItems } from "@/lib/search"; import { searchItems } from "@/lib/search";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { ArrowLeftIcon, CheckIcon, SearchIcon } from "lucide-react"; import { ArrowLeftIcon, CheckIcon, SearchIcon } from "lucide-react";
import { useMemo, useState, useTransition } from "react"; import { useMemo, useState, useTransition } from "react";
+1 -1
View File
@@ -2,7 +2,7 @@
import { searchItems } from "@/lib/search"; import { searchItems } from "@/lib/search";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { SearchIcon, Trash2Icon } from "lucide-react"; import { SearchIcon, Trash2Icon } from "lucide-react";
import { useMemo, useState, useTransition } from "react"; import { useMemo, useState, useTransition } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "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 { closestCenter, DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent } from "@dnd-kit/core";
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
+1 -1
View File
@@ -3,7 +3,7 @@
import { searchItems } from "@/lib/search"; import { searchItems } from "@/lib/search";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
ArrowLeftIcon, ArrowLeftIcon,
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import type { MouseEvent } from "react"; import type { MouseEvent } from "react";
@@ -1,6 +1,7 @@
"use client"; "use client";
import Image, { getImageProps } from "next/image"; import { getImageProps } from "next/image";
import Image from "@/components/ui/resilient-image";
import { import {
closestCenter, closestCenter,
DndContext, DndContext,
+1 -1
View File
@@ -2,7 +2,7 @@
import { searchItems } from "@/lib/search"; import { searchItems } from "@/lib/search";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect, useMemo, useRef, useState, useTransition, type ReactNode } from "react"; import { useEffect, useMemo, useRef, useState, useTransition, type ReactNode } from "react";
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { CircleHelpIcon } from "lucide-react"; import { CircleHelpIcon } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import type { ComponentProps, ReactNode } from "react"; import type { ComponentProps, ReactNode } from "react";
import { import {
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { UserRoundIcon } from "lucide-react"; import { UserRoundIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+1 -1
View File
@@ -2,7 +2,7 @@
import { useState } from "react"; import { useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { UserRoundIcon, XIcon } from "lucide-react"; import { UserRoundIcon, XIcon } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { ArtifactSelect, CharacterSelect, WeaponSelect } from "@/components/admin/ui/catalog-select"; import { ArtifactSelect, CharacterSelect, WeaponSelect } from "@/components/admin/ui/catalog-select";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { ArrowUpRightIcon } from "lucide-react"; import { ArrowUpRightIcon } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { MousePointerClickIcon } from "lucide-react"; import { MousePointerClickIcon } from "lucide-react";
import { Children, Fragment, type ReactNode, useState } from "react"; import { Children, Fragment, type ReactNode, useState } from "react";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
+1 -1
View File
@@ -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 { GuideExportBranding } from "@/components/public/guide-export-branding";
import { ExportText } from "@/components/public/export-language"; import { ExportText } from "@/components/public/export-language";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { elementIconFilter } from "@/lib/catalog/element"; import { elementIconFilter } from "@/lib/catalog/element";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
export function GuideExportBranding({ export function GuideExportBranding({
backgroundUrl, backgroundUrl,
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { useRef, type PointerEvent } from "react"; import { useRef, type PointerEvent } from "react";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { BookOpenIcon, MenuIcon } from "lucide-react"; import { BookOpenIcon, MenuIcon } from "lucide-react";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { Suspense } from "react"; import { Suspense } from "react";
import { PencilIcon } from "lucide-react"; import { PencilIcon } from "lucide-react";
+1 -1
View File
@@ -2,7 +2,7 @@
import { searchItems } from "@/lib/search"; import { searchItems } from "@/lib/search";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { SearchIcon, SlidersHorizontalIcon, StarIcon, UsersIcon, XIcon } from "lucide-react"; import { SearchIcon, SlidersHorizontalIcon, StarIcon, UsersIcon, XIcon } from "lucide-react";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import localFont from "next/font/local"; import localFont from "next/font/local";
import Link from "next/link"; import Link from "next/link";
import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react"; import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import Link from "next/link"; import Link from "next/link";
import { ArrowUpRightIcon, SearchIcon, XIcon } from "lucide-react"; import { ArrowUpRightIcon, SearchIcon, XIcon } from "lucide-react";
import { Fragment, useMemo, useState, type ReactNode } from "react"; import { Fragment, useMemo, useState, type ReactNode } from "react";
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { DownloadIcon } from "lucide-react"; import { DownloadIcon } from "lucide-react";
import { useState, type ReactNode } from "react"; import { useState, type ReactNode } from "react";
+1 -1
View File
@@ -1,4 +1,4 @@
import Image from "next/image"; import Image from "@/components/ui/resilient-image";
import { CheckIcon, XIcon } from "lucide-react"; import { CheckIcon, XIcon } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
+34
View File
@@ -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 (
<NextImage
{...props}
src={fallbackUrl ?? src}
unoptimized={unoptimized || Boolean(fallbackUrl)}
onError={(event) => {
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);
}}
/>
);
}