perf(site): inline small classic icons and favicon as base64
CI / check (push) Successful in 2m27s
CI / deploy (push) Successful in 1m34s
CI / publish-container (push) Successful in 50s

- New src/lib/site-icons.ts: base64 data URIs for the 13 tiny classic
  icons actually referenced in markup (100–2000 bytes), replacing extra
  requests with inline payloads. home.png, dynamic flags and currency
  sets stay on the filesystem.
- Default favicon is now served server-side as a base64 SVG data URI
  (memoized), while a DB-configured custom favicon still takes priority.
- Icons render through <Image unoptimized>, so data URIs pass through
  untouched on all affected pages (home, login, register, settings,
  navigation, auth top bar, client loading).
This commit is contained in:
openhands committed 2026-09-12 17:28:01 +02:00
1 parent 97012a78f6
commit e5e17d75ae
10 files changed
+128 -64

No files matched your search

+4 -3
View File
@@ -12,6 +12,7 @@ import { db, User } from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name";
import { captchaConfig } from "@/lib/services/captcha";
import { siteSettings } from "@/lib/services/site-settings";
import { ICON_FRIENDS, ICON_NAV_GOODY, ICON_NAV_ME } from "@/lib/site-icons";
export default async function LoginPage() {
const t = await getTranslations("pages.login");
@@ -117,7 +118,7 @@ export default async function LoginPage() {
{recentUsers.length > 0 && (
<SurfaceCard
title={th("online", { count: online, hotel: "" }).trim()}
icon="/assets/images/icons/navigation/goody.png"
icon={ICON_NAV_GOODY}
bodyClassName="p-4"
className="card-glow"
>
@@ -154,7 +155,7 @@ export default async function LoginPage() {
{latestUsers.length > 0 && (
<SurfaceCard
title="Newest citizens"
icon="/assets/images/icons/friends.png"
icon={ICON_FRIENDS}
bodyClassName="p-4"
className="card-glow"
>
@@ -193,7 +194,7 @@ export default async function LoginPage() {
<div className="flex-1">
<SurfaceCard
title={t("title")}
icon="/assets/images/icons/navigation/me.png"
icon={ICON_NAV_ME}
bodyClassName="p-6 sm:p-7"
>
<p
+21 -15
View File
@@ -6,6 +6,15 @@ import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { PublicLoadError } from "@/components/public/load-error";
import { publicReadFailure } from "@/lib/services/public-read";
import {
ICON_ARTICLE,
ICON_CAMERA,
ICON_CATALOG,
ICON_FRIENDS,
ICON_LIGHTHOUSE,
ICON_NAV_GOODY,
ICON_NAV_ME,
} from "@/lib/site-icons";
export const metadata: Metadata = {
title: "Home",
@@ -124,17 +133,17 @@ export default async function Home() {
{
title: tp("featuresAvatar"),
desc: tp("featuresAvatarDesc"),
icon: "/assets/images/icons/friends.png",
icon: ICON_FRIENDS,
},
{
title: tp("featuresFurniture"),
desc: tp("featuresFurnitureDesc"),
icon: "/assets/images/icons/catalog.png",
icon: ICON_CATALOG,
},
{
title: tp("featuresCommunity"),
desc: tp("featuresCommunityDesc"),
icon: "/assets/images/icons/navigation/goody.png",
icon: ICON_NAV_GOODY,
},
];
@@ -168,7 +177,7 @@ export default async function Home() {
/>
) : (
<Image
src="/assets/images/icons/lighthouse.png"
src={ICON_LIGHTHOUSE}
alt={hotelName}
width={28}
height={28}
@@ -393,25 +402,25 @@ export default async function Home() {
{
value: users,
label: tp("statsCitizens"),
icon: "/assets/images/icons/friends.png",
icon: ICON_FRIENDS,
live: false,
},
{
value: rooms,
label: tp("statsRooms"),
icon: "/assets/images/icons/catalog.png",
icon: ICON_CATALOG,
live: false,
},
{
value: online,
label: tp("statsOnline"),
icon: "/assets/images/icons/navigation/me.png",
icon: ICON_NAV_ME,
live: true,
},
{
value: articles?.length ?? null,
label: tp("statsArticles"),
icon: "/assets/images/icons/article.gif",
icon: ICON_ARTICLE,
live: false,
},
].map((s) => (
@@ -458,7 +467,7 @@ export default async function Home() {
<Reveal className="flex w-full flex-col gap-4 md:w-1/3 md:flex-shrink-0">
<SurfaceCard
title={th("login")}
icon="/assets/images/icons/navigation/me.png"
icon={ICON_NAV_ME}
bodyClassName="p-3.5 sm:p-4"
>
<HomeLoginForm
@@ -479,7 +488,7 @@ export default async function Home() {
recentUsers.length > 0 && (
<SurfaceCard
title={tp("recentUsers")}
icon="/assets/images/icons/navigation/goody.png"
icon={ICON_NAV_GOODY}
actionHref="/community"
actionLabel={tp("allUsers")}
bodyClassName="p-3.5 sm:p-4"
@@ -514,7 +523,7 @@ export default async function Home() {
<Reveal delay={0.15} className="flex w-full flex-col gap-4 md:w-2/3">
<SurfaceCard
title={tp("latestNews")}
icon="/assets/images/icons/article.gif"
icon={ICON_ARTICLE}
actionHref="/news"
actionLabel={tp("allNews")}
>
@@ -588,10 +597,7 @@ export default async function Home() {
) : (
recentPhotos.length > 0 && (
<Reveal>
<SurfaceCard
title={tp("recentPhotos")}
icon="/assets/images/icons/camera.png"
>
<SurfaceCard title={tp("recentPhotos")} icon={ICON_CAMERA}>
<div className="mx-auto grid max-w-md grid-cols-4 gap-2">
{recentPhotos.slice(0, 4).map((p) => (
<Link
+4 -3
View File
@@ -12,6 +12,7 @@ import { db, User } from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name";
import { captchaConfig } from "@/lib/services/captcha";
import { siteSettings } from "@/lib/services/site-settings";
import { ICON_FRIENDS, ICON_NAV_GOODY, ICON_NAV_ME } from "@/lib/site-icons";
export default async function RegisterPage() {
const tpr = await getTranslations("pages.register");
@@ -126,7 +127,7 @@ export default async function RegisterPage() {
{recentUsers.length > 0 && (
<SurfaceCard
title={tpr("whoIsOnline")}
icon="/assets/images/icons/navigation/goody.png"
icon={ICON_NAV_GOODY}
bodyClassName="p-4"
className="card-glow"
>
@@ -163,7 +164,7 @@ export default async function RegisterPage() {
{latestUsers.length > 0 && (
<SurfaceCard
title="Newest citizens"
icon="/assets/images/icons/friends.png"
icon={ICON_FRIENDS}
bodyClassName="p-4"
className="card-glow"
>
@@ -202,7 +203,7 @@ export default async function RegisterPage() {
<div className="flex-1 lg:sticky lg:top-6">
<SurfaceCard
title={tpr("title")}
icon="/assets/images/icons/navigation/me.png"
icon={ICON_NAV_ME}
bodyClassName="p-6 sm:p-7"
>
<p
+11 -6
View File
@@ -9,7 +9,12 @@ import { SurfaceCard } from "@/components/surface-card";
import { auth } from "@/lib/auth";
import { db, User } from "@/lib/db";
import { avatarImageUrl } from "@/lib/format";
import {
ICON_NAV_COMMUNITY,
ICON_NAV_GOODY,
ICON_NAV_ME,
ICON_STAR,
} from "@/lib/site-icons";
import ProfilePrivacySettings from "./ProfilePrivacySettings";
export default async function SettingsPage({
searchParams,
@@ -74,7 +79,7 @@ export default async function SettingsPage({
}}
>
<Image
src="/assets/images/icons/navigation/me.png"
src={ICON_NAV_ME}
alt=""
width={22}
height={22}
@@ -106,7 +111,7 @@ export default async function SettingsPage({
<Reveal delay={0.05}>
<SurfaceCard
title={t("publicProfile")}
icon="/assets/images/icons/navigation/me.png"
icon={ICON_NAV_ME}
bodyClassName="p-5 flex flex-col sm:flex-row items-center sm:items-center gap-5"
>
<div className="shrink-0">
@@ -161,7 +166,7 @@ export default async function SettingsPage({
<Reveal delay={0.1}>
<SurfaceCard
title={t("changeMotto")}
icon="/assets/images/icons/star.gif"
icon={ICON_STAR}
bodyClassName="p-5"
>
<p
@@ -211,7 +216,7 @@ export default async function SettingsPage({
<Reveal delay={0.15}>
<SurfaceCard
title={t("securityTitle")}
icon="/assets/images/icons/navigation/goody.png"
icon={ICON_NAV_GOODY}
bodyClassName="p-5"
>
<p
@@ -253,7 +258,7 @@ export default async function SettingsPage({
}}
>
<Image
src="/assets/images/icons/navigation/community.png"
src={ICON_NAV_COMMUNITY}
alt=""
width={20}
height={20}
+2 -1
View File
@@ -1,4 +1,5 @@
import Image from "next/image";
import { ICON_LIGHTHOUSE } from "@/lib/site-icons";
export default function ClientLoading() {
return (
@@ -27,7 +28,7 @@ export default function ClientLoading() {
}}
>
<Image
src="/assets/images/icons/lighthouse.png"
src={ICON_LIGHTHOUSE}
alt=""
width={44}
height={44}
+2 -1
View File
@@ -12,6 +12,7 @@ import { ScopedThemeVars } from "@/components/scoped-theme-vars";
import { ThemeScopeDetector } from "@/components/theme-scope-detector";
import { ThemeVars } from "@/components/theme-vars";
import { enforceSiteAccess } from "@/lib/access-guard";
import { getDefaultFaviconDataUri } from "@/lib/default-favicon";
import { resolveHotelName } from "@/lib/hotel-name";
import { siteSettings } from "@/lib/services/site-settings";
import "./globals.css";
@@ -40,7 +41,7 @@ export async function generateMetadata(): Promise<Metadata> {
const appUrl = process.env.APP_URL ?? "http://localhost:3000";
const hotel = await resolveHotelName();
const customFavicon = await siteSettings.get("cms_favicon");
const faviconUrl = customFavicon ?? "/favicon.svg";
const faviconUrl = customFavicon ?? getDefaultFaviconDataUri();
const icons = [
{ rel: "icon", url: faviconUrl },
{ rel: "shortcut icon", url: faviconUrl },