diff --git a/app/api/book-reviews/route.ts b/app/api/book-reviews/route.ts index ae4940e..549ae66 100644 --- a/app/api/book-reviews/route.ts +++ b/app/api/book-reviews/route.ts @@ -3,7 +3,9 @@ import { getBookReviews } from '@/lib/directus'; import { checkRateLimit, getClientIp } from '@/lib/auth'; export const runtime = 'nodejs'; -export const dynamic = 'force-dynamic'; +export const revalidate = 300; + +const CACHE_TTL = 300; // 5 minutes /** * GET /api/book-reviews @@ -25,31 +27,29 @@ export async function GET(request: NextRequest) { const locale = searchParams.get('locale') || 'en'; const reviews = await getBookReviews(locale); - + if (process.env.NODE_ENV === 'development') { console.log(`[API] Book Reviews geladen für ${locale}:`, reviews?.length || 0); } if (reviews && reviews.length > 0) { - return NextResponse.json({ - bookReviews: reviews, - source: 'directus' - }); + return NextResponse.json( + { bookReviews: reviews, source: 'directus' }, + { headers: { 'Cache-Control': `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } - return NextResponse.json({ - bookReviews: null, - source: 'fallback' - }); + return NextResponse.json( + { bookReviews: null, source: 'fallback' }, + { headers: { 'Cache-Control': `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } catch (error) { - console.error('Error loading book reviews:', error); + if (process.env.NODE_ENV === 'development') { + console.error('Error loading book reviews:', error); + } return NextResponse.json( - { - bookReviews: null, - error: 'Failed to load book reviews', - source: 'error' - }, + { bookReviews: null, error: 'Failed to load book reviews', source: 'error' }, { status: 500 } ); } diff --git a/app/api/content/page/route.ts b/app/api/content/page/route.ts index 13fb875..4e89980 100644 --- a/app/api/content/page/route.ts +++ b/app/api/content/page/route.ts @@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from "next/server"; import { getContentByKey } from "@/lib/content"; import { getContentPage } from "@/lib/directus"; +const CACHE_TTL = 300; // 5 minutes + export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const key = searchParams.get("key"); @@ -15,21 +17,32 @@ export async function GET(request: NextRequest) { // 1) Try Directus first const directusPage = await getContentPage(key, locale); if (directusPage) { - return NextResponse.json({ - content: { - title: directusPage.title, - slug: directusPage.slug, - locale: directusPage.locale || locale, - content: directusPage.content, + return NextResponse.json( + { + content: { + title: directusPage.title, + slug: directusPage.slug, + locale: directusPage.locale || locale, + content: directusPage.content, + }, + source: "directus", }, - source: "directus", - }); + { headers: { "Cache-Control": `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } // 2) Fallback: PostgreSQL const translation = await getContentByKey({ key, locale }); - if (!translation) return NextResponse.json({ content: null }); - return NextResponse.json({ content: translation, source: "postgresql" }); + if (!translation) { + return NextResponse.json( + { content: null }, + { headers: { "Cache-Control": `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); + } + return NextResponse.json( + { content: translation, source: "postgresql" }, + { headers: { "Cache-Control": `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } catch (error) { // If DB isn't migrated/available, fail soft so the UI can fall back to next-intl strings. if (process.env.NODE_ENV === "development") { diff --git a/app/api/hobbies/route.ts b/app/api/hobbies/route.ts index e6dd4a9..7b3698b 100644 --- a/app/api/hobbies/route.ts +++ b/app/api/hobbies/route.ts @@ -3,7 +3,9 @@ import { getHobbies } from '@/lib/directus'; import { checkRateLimit, getClientIp } from '@/lib/auth'; export const runtime = 'nodejs'; -export const dynamic = 'force-dynamic'; +export const revalidate = 300; + +const CACHE_TTL = 300; // 5 minutes /** * GET /api/hobbies @@ -28,26 +30,24 @@ export async function GET(request: NextRequest) { const hobbies = await getHobbies(locale); if (hobbies && hobbies.length > 0) { - return NextResponse.json({ - hobbies, - source: 'directus' - }); + return NextResponse.json( + { hobbies, source: 'directus' }, + { headers: { 'Cache-Control': `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } // Fallback: return empty (component will use hardcoded fallback) - return NextResponse.json({ - hobbies: null, - source: 'fallback' - }); + return NextResponse.json( + { hobbies: null, source: 'fallback' }, + { headers: { 'Cache-Control': `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } catch (error) { - console.error('Error loading hobbies:', error); + if (process.env.NODE_ENV === 'development') { + console.error('Error loading hobbies:', error); + } return NextResponse.json( - { - hobbies: null, - error: 'Failed to load hobbies', - source: 'error' - }, + { hobbies: null, error: 'Failed to load hobbies', source: 'error' }, { status: 500 } ); } diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts index 7135ebb..20b4d53 100644 --- a/app/api/messages/route.ts +++ b/app/api/messages/route.ts @@ -1,13 +1,18 @@ import { NextRequest, NextResponse } from "next/server"; import { getMessages } from "@/lib/directus"; +const CACHE_TTL = 300; // 5 minutes + export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const locale = searchParams.get("locale") || "en"; try { const messages = await getMessages(locale); - return NextResponse.json({ messages }); + return NextResponse.json( + { messages }, + { headers: { "Cache-Control": `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } catch { return NextResponse.json({ messages: {} }, { status: 500 }); } diff --git a/app/api/snippets/route.ts b/app/api/snippets/route.ts index c1ed4e3..39386f8 100644 --- a/app/api/snippets/route.ts +++ b/app/api/snippets/route.ts @@ -1,6 +1,8 @@ import { NextRequest, NextResponse } from 'next/server'; import { getSnippets } from '@/lib/directus'; +const CACHE_TTL = 300; // 5 minutes + export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); @@ -9,9 +11,10 @@ export async function GET(request: NextRequest) { const snippets = await getSnippets(limit, featured); - return NextResponse.json({ - snippets: snippets || [] - }); + return NextResponse.json( + { snippets: snippets || [] }, + { headers: { 'Cache-Control': `public, s-maxage=${CACHE_TTL}, stale-while-revalidate=${CACHE_TTL * 2}` } } + ); } catch (_error) { return NextResponse.json({ error: 'Failed to fetch snippets' }, { status: 500 }); } diff --git a/app/api/tech-stack/route.ts b/app/api/tech-stack/route.ts index b9739dc..98d6b5b 100644 --- a/app/api/tech-stack/route.ts +++ b/app/api/tech-stack/route.ts @@ -3,14 +3,15 @@ import { getTechStack } from '@/lib/directus'; import { checkRateLimit, getClientIp } from '@/lib/auth'; export const runtime = 'nodejs'; +export const revalidate = 300; const CACHE_TTL = 300; // 5 minutes /** * GET /api/tech-stack - * + * * Loads Tech Stack from Directus with fallback to static data - * + * * Query params: * - locale: en or de (default: en) */ diff --git a/app/components/About.tsx b/app/components/About.tsx index 4a04a46..2c1d4df 100644 --- a/app/components/About.tsx +++ b/app/components/About.tsx @@ -34,12 +34,11 @@ const About = () => { useEffect(() => { const fetchData = async () => { try { - const [cmsRes, techRes, hobbiesRes, msgRes, booksRes, snippetsRes] = await Promise.all([ + const [cmsRes, techRes, hobbiesRes, msgRes, snippetsRes] = await Promise.all([ fetch(`/api/content/page?key=home-about&locale=${locale}`), fetch(`/api/tech-stack?locale=${locale}`), fetch(`/api/hobbies?locale=${locale}`), fetch(`/api/messages?locale=${locale}`), - fetch(`/api/book-reviews?locale=${locale}`), fetch(`/api/snippets?limit=3&featured=true`) ]); @@ -57,9 +56,6 @@ const About = () => { const snippetsData = await snippetsRes.json(); if (snippetsData?.snippets) setSnippets(snippetsData.snippets); - - await booksRes.json(); - // Books data is available but we don't need to track count anymore } catch (error) { console.error("About data fetch failed:", error); } finally { diff --git a/app/components/ActivityFeed.tsx b/app/components/ActivityFeed.tsx index 770fe77..e6ecdd3 100644 --- a/app/components/ActivityFeed.tsx +++ b/app/components/ActivityFeed.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Disc3, Gamepad2, Zap, Quote as QuoteIcon } from "lucide-react"; import { useTranslations } from "next-intl"; +import Image from "next/image"; interface CustomActivity { [key: string]: unknown; @@ -172,7 +173,7 @@ export default function ActivityFeed({
{data.gaming.image && (
- {data.gaming.name} + {data.gaming.name}
)}
@@ -215,10 +216,12 @@ export default function ActivityFeed({
- Album Art
diff --git a/app/components/BentoChat.tsx b/app/components/BentoChat.tsx index 46c81f3..2a2170d 100644 --- a/app/components/BentoChat.tsx +++ b/app/components/BentoChat.tsx @@ -105,7 +105,7 @@ export default function BentoChat() { placeholder="Ask me..." className="w-full bg-white dark:bg-stone-800 border border-stone-200 dark:border-stone-700 rounded-2xl py-3 pl-4 pr-12 text-sm focus:outline-none focus:ring-2 focus:ring-liquid-purple/30 transition-all shadow-inner dark:text-white" /> -
diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx index 4ed4699..0831bd6 100644 --- a/app/components/Contact.tsx +++ b/app/components/Contact.tsx @@ -192,7 +192,7 @@ const Contact = () => {
-

Connect

+

Connect

Online diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index 6197f59..f6231b5 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -115,7 +115,7 @@ const Hero = () => { >
- Dennis Konkol + Dennis Konkol
diff --git a/app/components/ShaderGradientBackground.tsx b/app/components/ShaderGradientBackground.tsx index 36d3462..8f7240c 100644 --- a/app/components/ShaderGradientBackground.tsx +++ b/app/components/ShaderGradientBackground.tsx @@ -31,7 +31,7 @@ const ShaderGradientBackground = () => { { { +