From eb5acbc4a214b76dd639b5fa725500870d24398e Mon Sep 17 00:00:00 2001 From: Simon Moses Onazi Date: Fri, 20 Mar 2026 14:04:39 +0100 Subject: [PATCH 1/4] Feat: Integrate website information for dynamic metadata and favicon --- src/lib/website-info.ts | 14 ++++++++++++++ src/routes/__root.tsx | 30 ++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 src/lib/website-info.ts diff --git a/src/lib/website-info.ts b/src/lib/website-info.ts new file mode 100644 index 0000000..122dbf4 --- /dev/null +++ b/src/lib/website-info.ts @@ -0,0 +1,14 @@ + +export type WebsiteInfo = { + title: string | null; + description: string | null; + faviconUrl: string | null; + ogImageUrl: string | null; +}; + +export const websiteInfo: WebsiteInfo = { + title: "Imagine App", + description: null, + faviconUrl: null, + ogImageUrl: null, +}; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 0e3a570..1cc3a8d 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -16,6 +16,7 @@ import { OGImageConfig, OGMetaTags, } from '@/lib/og-config' +import { websiteInfo } from '@/lib/website-info' interface MyRouterContext { queryClient: QueryClient @@ -53,17 +54,31 @@ export const Route = createRootRouteWithContext()({ isCustom: false, } - const ogImageUrl = generateOGImageUrl(config, baseUrl) + const ogImageUrl = websiteInfo.ogImageUrl ?? generateOGImageUrl(config, baseUrl) const metadata: OGMetaTags = { - title: 'Imagine App', - description: 'Build something real', + title: websiteInfo.title ?? 'Imagine App', + description: websiteInfo.description ?? '', image: ogImageUrl, url: typeof window !== 'undefined' ? window.location.href : baseUrl, } const ogTags = createOGMetaTags(metadata) + const links: Array> = [ + { + rel: 'stylesheet', + href: appCss, + }, + ] + + if (websiteInfo.faviconUrl) { + links.push({ + rel: 'icon', + href: websiteInfo.faviconUrl, + }) + } + return { meta: [ { @@ -74,16 +89,11 @@ export const Route = createRootRouteWithContext()({ content: 'width=device-width, initial-scale=1', }, { - title: 'Imagine App', + title: websiteInfo.title ?? 'Imagine App', }, ...ogTags.meta, ], - links: [ - { - rel: 'stylesheet', - href: appCss, - }, - ], + links, scripts: [...scripts], } }, From 448c4b03f4cbd4258b91eae120e830e188b84696 Mon Sep 17 00:00:00 2001 From: Simon Moses Onazi Date: Tue, 24 Mar 2026 12:26:37 +0100 Subject: [PATCH 2/4] Feat: Implement applyWebsiteInfoHead function for dynamic metadata handling --- src/lib/apply-website-info-head.ts | 89 ++++++++++++++++++++++++++++++ src/lib/website-info.ts | 6 ++ src/routes/__root.tsx | 36 +++++------- 3 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 src/lib/apply-website-info-head.ts diff --git a/src/lib/apply-website-info-head.ts b/src/lib/apply-website-info-head.ts new file mode 100644 index 0000000..5e5509c --- /dev/null +++ b/src/lib/apply-website-info-head.ts @@ -0,0 +1,89 @@ +/** + * IMAGINE-MANAGED FILE. + * Do not edit this file manually or with AI. + * Changes may be overwritten by Imagine. + */ + +import { websiteInfo } from "./website-info"; + +type HeadMetaEntry = Record; +type HeadLinkEntry = Record; + +function withoutWebsiteInfoMeta(meta: HeadMetaEntry[]) { + return meta.filter((entry) => { + if (typeof entry.title === "string") { + return false; + } + + const key = + typeof entry.name === "string" + ? entry.name + : typeof entry.property === "string" + ? entry.property + : ""; + return ![ + "description", + "og:title", + "og:description", + "og:image", + "og:type", + "twitter:card", + "twitter:title", + "twitter:description", + "twitter:image", + ].includes(key); + }); +} + +function withoutWebsiteInfoLinks(links: HeadLinkEntry[]) { + return links.filter((entry) => { + const rel = typeof entry.rel === "string" ? entry.rel : ""; + return rel !== "icon" && rel !== "shortcut icon"; + }); +} + +export function applyWebsiteInfoHead< + T extends { + meta?: HeadMetaEntry[]; + links?: HeadLinkEntry[]; + [key: string]: unknown; + }, +>(input: T) { + const meta = withoutWebsiteInfoMeta(input.meta ?? []); + const links = withoutWebsiteInfoLinks(input.links ?? []); + const title = websiteInfo.title ?? "Imagine App"; + + return { + ...input, + meta: [ + ...meta, + { title }, + { property: "og:title", content: title }, + { property: "og:type", content: "website" }, + { + name: "twitter:card", + content: websiteInfo.ogImageUrl ? "summary_large_image" : "summary", + }, + { name: "twitter:title", content: title }, + ...(websiteInfo.description + ? [ + { name: "description", content: websiteInfo.description }, + { property: "og:description", content: websiteInfo.description }, + { name: "twitter:description", content: websiteInfo.description }, + ] + : []), + ...(websiteInfo.ogImageUrl + ? [ + { property: "og:image", content: websiteInfo.ogImageUrl }, + { name: "twitter:image", content: websiteInfo.ogImageUrl }, + ] + : []), + ], + links: [ + ...links, + ...(websiteInfo.faviconUrl + ? [{ rel: "icon", href: websiteInfo.faviconUrl }] + : []), + ], + } satisfies T; +} diff --git a/src/lib/website-info.ts b/src/lib/website-info.ts index 122dbf4..382399f 100644 --- a/src/lib/website-info.ts +++ b/src/lib/website-info.ts @@ -1,3 +1,9 @@ +/** + * IMAGINE-MANAGED FILE. + * Do not edit this file manually or with AI. + * Update Website Info from the Imagine Studio settings. + * Changes may be overwritten by Imagine. + */ export type WebsiteInfo = { title: string | null; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 1cc3a8d..fa62803 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -16,7 +16,7 @@ import { OGImageConfig, OGMetaTags, } from '@/lib/og-config' -import { websiteInfo } from '@/lib/website-info' +import { applyWebsiteInfoHead } from "@/lib/apply-website-info-head"; interface MyRouterContext { queryClient: QueryClient @@ -44,6 +44,7 @@ export const Route = createRootRouteWithContext()({ baseUrl, } }, + head: ({ loaderData }) => { const baseUrl = typeof window !== 'undefined' @@ -54,32 +55,18 @@ export const Route = createRootRouteWithContext()({ isCustom: false, } - const ogImageUrl = websiteInfo.ogImageUrl ?? generateOGImageUrl(config, baseUrl) + const ogImageUrl = generateOGImageUrl(config, baseUrl) const metadata: OGMetaTags = { - title: websiteInfo.title ?? 'Imagine App', - description: websiteInfo.description ?? '', + title: 'Imagine App', + description: 'Build something real', image: ogImageUrl, url: typeof window !== 'undefined' ? window.location.href : baseUrl, } const ogTags = createOGMetaTags(metadata) - const links: Array> = [ - { - rel: 'stylesheet', - href: appCss, - }, - ] - - if (websiteInfo.faviconUrl) { - links.push({ - rel: 'icon', - href: websiteInfo.faviconUrl, - }) - } - - return { + return applyWebsiteInfoHead({ meta: [ { charSet: 'utf-8', @@ -89,13 +76,18 @@ export const Route = createRootRouteWithContext()({ content: 'width=device-width, initial-scale=1', }, { - title: websiteInfo.title ?? 'Imagine App', + title: 'Imagine App', }, ...ogTags.meta, ], - links, + links: [ + { + rel: 'stylesheet', + href: appCss, + }, + ], scripts: [...scripts], - } + }) }, shellComponent: RootDocument, From c307fcf62a350d7ad211ed6e9c6a5f61c9d744e9 Mon Sep 17 00:00:00 2001 From: Simon Moses Onazi Date: Tue, 24 Mar 2026 12:32:01 +0100 Subject: [PATCH 3/4] Refactor code structure for improved readability and maintainability --- src/lib/apply-website-info-head.ts | 80 ++++++------- src/lib/website-info.ts | 14 +-- src/routes/__root.tsx | 4 +- src/routes/_api/og.tsx | 176 ++++++++++++++--------------- 4 files changed, 135 insertions(+), 139 deletions(-) diff --git a/src/lib/apply-website-info-head.ts b/src/lib/apply-website-info-head.ts index 5e5509c..82da842 100644 --- a/src/lib/apply-website-info-head.ts +++ b/src/lib/apply-website-info-head.ts @@ -4,86 +4,86 @@ * Changes may be overwritten by Imagine. */ -import { websiteInfo } from "./website-info"; +import { websiteInfo } from './website-info' -type HeadMetaEntry = Record; -type HeadLinkEntry = Record; +type HeadMetaEntry = Record +type HeadLinkEntry = Record function withoutWebsiteInfoMeta(meta: HeadMetaEntry[]) { return meta.filter((entry) => { - if (typeof entry.title === "string") { - return false; + if (typeof entry.title === 'string') { + return false } const key = - typeof entry.name === "string" + typeof entry.name === 'string' ? entry.name - : typeof entry.property === "string" + : typeof entry.property === 'string' ? entry.property - : ""; + : '' return ![ - "description", - "og:title", - "og:description", - "og:image", - "og:type", - "twitter:card", - "twitter:title", - "twitter:description", - "twitter:image", - ].includes(key); - }); + 'description', + 'og:title', + 'og:description', + 'og:image', + 'og:type', + 'twitter:card', + 'twitter:title', + 'twitter:description', + 'twitter:image', + ].includes(key) + }) } function withoutWebsiteInfoLinks(links: HeadLinkEntry[]) { return links.filter((entry) => { - const rel = typeof entry.rel === "string" ? entry.rel : ""; - return rel !== "icon" && rel !== "shortcut icon"; - }); + const rel = typeof entry.rel === 'string' ? entry.rel : '' + return rel !== 'icon' && rel !== 'shortcut icon' + }) } export function applyWebsiteInfoHead< T extends { - meta?: HeadMetaEntry[]; - links?: HeadLinkEntry[]; - [key: string]: unknown; + meta?: HeadMetaEntry[] + links?: HeadLinkEntry[] + [key: string]: unknown }, >(input: T) { - const meta = withoutWebsiteInfoMeta(input.meta ?? []); - const links = withoutWebsiteInfoLinks(input.links ?? []); - const title = websiteInfo.title ?? "Imagine App"; + const meta = withoutWebsiteInfoMeta(input.meta ?? []) + const links = withoutWebsiteInfoLinks(input.links ?? []) + const title = websiteInfo.title ?? 'Imagine App' return { ...input, meta: [ ...meta, { title }, - { property: "og:title", content: title }, - { property: "og:type", content: "website" }, + { property: 'og:title', content: title }, + { property: 'og:type', content: 'website' }, { - name: "twitter:card", - content: websiteInfo.ogImageUrl ? "summary_large_image" : "summary", + name: 'twitter:card', + content: websiteInfo.ogImageUrl ? 'summary_large_image' : 'summary', }, - { name: "twitter:title", content: title }, + { name: 'twitter:title', content: title }, ...(websiteInfo.description ? [ - { name: "description", content: websiteInfo.description }, - { property: "og:description", content: websiteInfo.description }, - { name: "twitter:description", content: websiteInfo.description }, + { name: 'description', content: websiteInfo.description }, + { property: 'og:description', content: websiteInfo.description }, + { name: 'twitter:description', content: websiteInfo.description }, ] : []), ...(websiteInfo.ogImageUrl ? [ - { property: "og:image", content: websiteInfo.ogImageUrl }, - { name: "twitter:image", content: websiteInfo.ogImageUrl }, + { property: 'og:image', content: websiteInfo.ogImageUrl }, + { name: 'twitter:image', content: websiteInfo.ogImageUrl }, ] : []), ], links: [ ...links, ...(websiteInfo.faviconUrl - ? [{ rel: "icon", href: websiteInfo.faviconUrl }] + ? [{ rel: 'icon', href: websiteInfo.faviconUrl }] : []), ], - } satisfies T; + } satisfies T } diff --git a/src/lib/website-info.ts b/src/lib/website-info.ts index 382399f..8272764 100644 --- a/src/lib/website-info.ts +++ b/src/lib/website-info.ts @@ -6,15 +6,15 @@ */ export type WebsiteInfo = { - title: string | null; - description: string | null; - faviconUrl: string | null; - ogImageUrl: string | null; -}; + title: string | null + description: string | null + faviconUrl: string | null + ogImageUrl: string | null +} export const websiteInfo: WebsiteInfo = { - title: "Imagine App", + title: 'Imagine App', description: null, faviconUrl: null, ogImageUrl: null, -}; +} diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index fa62803..976521b 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -16,7 +16,7 @@ import { OGImageConfig, OGMetaTags, } from '@/lib/og-config' -import { applyWebsiteInfoHead } from "@/lib/apply-website-info-head"; +import { applyWebsiteInfoHead } from '@/lib/apply-website-info-head' interface MyRouterContext { queryClient: QueryClient @@ -44,7 +44,7 @@ export const Route = createRootRouteWithContext()({ baseUrl, } }, - + head: ({ loaderData }) => { const baseUrl = typeof window !== 'undefined' diff --git a/src/routes/_api/og.tsx b/src/routes/_api/og.tsx index 03091e1..6bd9ed8 100644 --- a/src/routes/_api/og.tsx +++ b/src/routes/_api/og.tsx @@ -54,40 +54,38 @@ export const Route = createFileRoute('/_api/og')({ // Create a composite image with screenshot overlay return new ImageResponse( - ( -
+ {/* Background image */} + - {/* Background image */} + /> + {/* Screenshot overlay - only render if screenshot was successful */} + {screenshotArrayBuffer && ( - {/* Screenshot overlay - only render if screenshot was successful */} - {screenshotArrayBuffer && ( - - )} -
- ), + )} + , { width: 1200, height: 630, @@ -122,99 +120,97 @@ export const Route = createFileRoute('/_api/og')({ const fontData = await loadGoogleFont('Inter', text) return new ImageResponse( - ( +
+ {/* Content section */}
- {/* Content section */}
+ {/* Logo */}
- {/* Logo */} -
- - - - - - - - - - -
+ + + + + + + + + +
-

+ {title} +

+ + {description && ( +

- {title} - - - {description && ( -

- {description} -

- )} -
+ {description} +

+ )}
- ), + , { width, height, From cc537801c4447640d535390862ec866d3efc3c78 Mon Sep 17 00:00:00 2001 From: Simon Moses Onazi Date: Tue, 24 Mar 2026 12:39:20 +0100 Subject: [PATCH 4/4] format og.tsx --- src/routes/_api/og.tsx | 176 +++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 86 deletions(-) diff --git a/src/routes/_api/og.tsx b/src/routes/_api/og.tsx index 6bd9ed8..03091e1 100644 --- a/src/routes/_api/og.tsx +++ b/src/routes/_api/og.tsx @@ -54,38 +54,40 @@ export const Route = createFileRoute('/_api/og')({ // Create a composite image with screenshot overlay return new ImageResponse( -
- {/* Background image */} - - {/* Screenshot overlay - only render if screenshot was successful */} - {screenshotArrayBuffer && ( + > + {/* Background image */} - )} -
, + {/* Screenshot overlay - only render if screenshot was successful */} + {screenshotArrayBuffer && ( + + )} + + ), { width: 1200, height: 630, @@ -120,97 +122,99 @@ export const Route = createFileRoute('/_api/og')({ const fontData = await loadGoogleFont('Inter', text) return new ImageResponse( -
- {/* Content section */} + (
+ {/* Content section */}
- {/* Logo */}
- - - - - - - - - - -
+ + + + + + + + + + +
-

- {title} -

- - {description && ( -

- {description} -

- )} + {title} + + + {description && ( +

+ {description} +

+ )} +
- , + ), { width, height,