feat: optimize favicon loading across the application using Next.js image component for better performance and caching
This commit is contained in:
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
- **Smoother loading experience.** Pages now show a subtle preview of the layout while data loads instead of a blank screen or spinner. This applies everywhere — dashboards, settings, uptime, funnels, notifications, billing, and detail modals.
|
||||
- **No more loading flicker.** Fast-loading pages no longer flash a loading state for a split second before showing content.
|
||||
- **Clearer error messages.** When something goes wrong, the error message now tells you what failed (e.g. "Failed to load uptime monitors") instead of a generic "Failed to load data".
|
||||
- **Faster favicon loading.** Site icons in the dashboard, referrers, and campaigns now use Next.js image optimization for better caching and lazy loading.
|
||||
|
||||
## [0.10.0-alpha] - 2026-02-21
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { useParams, useSearchParams, useRouter } from 'next/navigation'
|
||||
import { getPublicDashboard, getPublicStats, getPublicDailyStats, getPublicRealtime, getPublicPerformanceByPage, type DashboardData, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats'
|
||||
import { toast } from '@ciphera-net/ui'
|
||||
@@ -282,13 +283,16 @@ export default function PublicDashboardPage() {
|
||||
<span className="text-sm font-medium text-brand-orange uppercase tracking-wider">Public Dashboard</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white flex items-center gap-3">
|
||||
<img
|
||||
<Image
|
||||
src={`https://www.google.com/s2/favicons?domain=${site.domain}&sz=64`}
|
||||
alt={site.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-8 h-8 rounded-lg"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src = '/globe.svg'
|
||||
}}
|
||||
unoptimized
|
||||
/>
|
||||
{site.domain}
|
||||
</h1>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { formatNumber } from '@ciphera-net/ui'
|
||||
import { Modal, ArrowRightIcon, Button } from '@ciphera-net/ui'
|
||||
import { TableSkeleton } from '@/components/skeletons'
|
||||
@@ -111,11 +112,14 @@ export default function Campaigns({ siteId, dateRange }: CampaignsProps) {
|
||||
const useFavicon = faviconUrl && !faviconFailed.has(source)
|
||||
if (useFavicon) {
|
||||
return (
|
||||
<img
|
||||
<Image
|
||||
src={faviconUrl}
|
||||
alt=""
|
||||
width={20}
|
||||
height={20}
|
||||
className="w-5 h-5 flex-shrink-0 rounded object-contain"
|
||||
onError={() => setFaviconFailed((prev) => new Set(prev).add(source))}
|
||||
unoptimized
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { formatNumber } from '@ciphera-net/ui'
|
||||
import { getReferrerDisplayName, getReferrerFavicon, getReferrerIcon, mergeReferrersByDisplayName } from '@/lib/utils/icons'
|
||||
import { Modal, GlobeIcon } from '@ciphera-net/ui'
|
||||
@@ -39,11 +40,14 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
|
||||
const useFavicon = faviconUrl && !faviconFailed.has(referrer)
|
||||
if (useFavicon) {
|
||||
return (
|
||||
<img
|
||||
<Image
|
||||
src={faviconUrl}
|
||||
alt=""
|
||||
width={20}
|
||||
height={20}
|
||||
className="w-5 h-5 flex-shrink-0 rounded object-contain"
|
||||
onError={() => setFaviconFailed((prev) => new Set(prev).add(referrer))}
|
||||
unoptimized
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { Site } from '@/lib/api/sites'
|
||||
import type { Stats } from '@/lib/api/stats'
|
||||
import { formatNumber } from '@ciphera-net/ui'
|
||||
@@ -34,10 +35,13 @@ function SiteCard({ site, stats, statsLoading, onDelete, canDelete }: SiteCardPr
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-12 w-12 overflow-hidden rounded-lg border border-neutral-100 bg-neutral-50 p-1 dark:border-neutral-800 dark:bg-neutral-800">
|
||||
<img
|
||||
<Image
|
||||
src={`https://www.google.com/s2/favicons?domain=${site.domain}&sz=64`}
|
||||
alt={site.name}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-full w-full object-contain"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -12,6 +12,15 @@ const nextConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
// * Privacy-first: Disable analytics and telemetry
|
||||
productionBrowserSourceMaps: false,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'www.google.com',
|
||||
pathname: '/s2/favicons**',
|
||||
},
|
||||
],
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user