From d76a9b2d15e2c2dd1a2c8f1c46242177a67fc8bc Mon Sep 17 00:00:00 2001 From: OriginalByteMe Date: Tue, 4 Nov 2025 19:48:12 +0800 Subject: [PATCH 1/2] make track fetch from api --- app/share/[id]/opengraph-image.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app/share/[id]/opengraph-image.tsx b/app/share/[id]/opengraph-image.tsx index 785bf17..44fbbbc 100644 --- a/app/share/[id]/opengraph-image.tsx +++ b/app/share/[id]/opengraph-image.tsx @@ -1,5 +1,5 @@ import { ImageResponse } from 'next/og' -import { getTrackCached } from '@/lib/get-track-cached' +import { SpotifyTrack } from '@/app/utils/interfaces' import { readFile } from 'node:fs/promises' import { join } from 'node:path' @@ -76,9 +76,18 @@ async function fetchWithRetry( // Image generation export default async function Image({ params }: { params: { id: string } }) { try { - const track = await getTrackCached(params.id) + // Fetch track data from API endpoint + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || + (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'http://localhost:3000') - if (!track) { + const response = await fetch(`${baseUrl}/api/track/${params.id}`, { + next: { + revalidate: 3600, // Match OG image cache duration + tags: [`track-${params.id}`] // Enable manual invalidation + } + }) + + if (!response.ok) { // Return default Moodify image if track not found const logoSvg = await readFile(join(process.cwd(), 'public/logo.svg'), 'utf-8') @@ -122,6 +131,9 @@ export default async function Image({ params }: { params: { id: string } }) { ) } + // Parse track data from API response + const track: SpotifyTrack = await response.json() + // Use track palette or fallback to default const palette = track.colourPalette && track.colourPalette.length >= 5 ? track.colourPalette From 9bc20fd0feb72633da144bcfbb95e38889863f93 Mon Sep 17 00:00:00 2001 From: OriginalByteMe Date: Tue, 4 Nov 2025 19:57:10 +0800 Subject: [PATCH 2/2] file fetching using url instead of local filesystem --- app/opengraph-image.tsx | 23 ++++++++++++++++++++--- app/share/[id]/opengraph-image.tsx | 25 +++++++++++++++++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/app/opengraph-image.tsx b/app/opengraph-image.tsx index 4d68538..58fa102 100644 --- a/app/opengraph-image.tsx +++ b/app/opengraph-image.tsx @@ -1,6 +1,4 @@ import { ImageResponse } from 'next/og' -import { readFile } from 'node:fs/promises' -import { join } from 'node:path' // Image metadata export const alt = 'Moodify - Pick a song, paint the mood' @@ -28,10 +26,29 @@ function rgbToHex(rgb: number[]): string { return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}` } +// In-memory cache for logo to avoid repeated fetches +let logoCache: string | null = null + +// Helper to fetch logo from public URL (serverless-compatible) +async function getLogo(): Promise { + if (logoCache) return logoCache + + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || + (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'http://localhost:3000') + + const response = await fetch(`${baseUrl}/logo.svg`) + if (!response.ok) { + throw new Error(`Failed to fetch logo: ${response.status}`) + } + + logoCache = await response.text() + return logoCache +} + // Image generation export default async function Image() { // Load Moodify logo - const logoSvg = await readFile(join(process.cwd(), 'public/logo.svg'), 'utf-8') + const logoSvg = await getLogo() const logoDataUrl = `data:image/svg+xml;base64,${Buffer.from(logoSvg).toString('base64')}` // Get hex colors for gradient diff --git a/app/share/[id]/opengraph-image.tsx b/app/share/[id]/opengraph-image.tsx index 44fbbbc..5db08b3 100644 --- a/app/share/[id]/opengraph-image.tsx +++ b/app/share/[id]/opengraph-image.tsx @@ -1,7 +1,5 @@ import { ImageResponse } from 'next/og' import { SpotifyTrack } from '@/app/utils/interfaces' -import { readFile } from 'node:fs/promises' -import { join } from 'node:path' // Image metadata export const alt = 'Moodify track visualization' @@ -73,6 +71,25 @@ async function fetchWithRetry( throw new Error('Unexpected: retry loop completed without return or throw') } +// In-memory cache for logo to avoid repeated fetches +let logoCache: string | null = null + +// Helper to fetch logo from public URL (serverless-compatible) +async function getLogo(): Promise { + if (logoCache) return logoCache + + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || + (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'http://localhost:3000') + + const response = await fetch(`${baseUrl}/logo.svg`) + if (!response.ok) { + throw new Error(`Failed to fetch logo: ${response.status}`) + } + + logoCache = await response.text() + return logoCache +} + // Image generation export default async function Image({ params }: { params: { id: string } }) { try { @@ -89,7 +106,7 @@ export default async function Image({ params }: { params: { id: string } }) { if (!response.ok) { // Return default Moodify image if track not found - const logoSvg = await readFile(join(process.cwd(), 'public/logo.svg'), 'utf-8') + const logoSvg = await getLogo() return new ImageResponse( ( @@ -149,7 +166,7 @@ export default async function Image({ params }: { params: { id: string } }) { const useDefaultLogo = !albumArt // Load logo for branding - const logoSvg = await readFile(join(process.cwd(), 'public/logo.svg'), 'utf-8') + const logoSvg = await getLogo() const logoDataUrl = `data:image/svg+xml;base64,${Buffer.from(logoSvg).toString('base64')}` let albumArtDataUrl: string | null = null