Migrate from Vite to Next.js 15 with SSR

- Replace Vite + React Router with Next.js 15 App Router
- Implement i18n with next-intl (URL-based: /de, /en, /sr)
- Add SSR/SSG for all pages (48 static pages generated)
- Setup Supabase SSR client for auth
- Migrate all pages: Home, About, Portfolio, Blog, Contact, Login, Dashboard, Imprint, Privacy, Terms
- Add Docker support with standalone output
- Replace i18next with next-intl JSON translations
- Use next/image for optimized images

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-17 01:00:33 +01:00
co-authored by Claude Opus 4.5
parent a66f51b9a2
commit b1ec7b4d61
281 changed files with 8024 additions and 8901 deletions
+93
View File
@@ -0,0 +1,93 @@
import { notFound } from 'next/navigation';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { Inter } from 'next/font/google';
import { locales, type Locale } from '@/i18n/config';
import type { Metadata } from 'next';
import '../globals.css';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
type Props = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
};
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const titles: Record<Locale, string> = {
de: 'Damjan Savic | AI & Automation Specialist aus Koeln',
en: 'Damjan Savic | AI & Automation Specialist from Cologne',
sr: 'Damjan Savic | AI & Automation Specialist iz Kelna',
};
const descriptions: Record<Locale, string> = {
de: 'Entwicklung von KI-Agenten und Automatisierungsloesungen. Von Voice-AI-Plattformen bis zu autonomen Web-Agenten.',
en: 'Building AI agents and automation solutions. From voice AI platforms to autonomous web agents.',
sr: 'Razvoj AI agenata i resenja za automatizaciju. Od voice AI platformi do autonomnih web agenata.',
};
return {
title: {
template: '%s | Damjan Savic',
default: titles[locale as Locale] || titles.de,
},
description: descriptions[locale as Locale] || descriptions.de,
alternates: {
canonical: `/${locale}`,
languages: {
de: '/de',
en: '/en',
sr: '/sr',
},
},
openGraph: {
type: 'website',
locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US',
siteName: 'Damjan Savic',
images: [
{
url: '/images/og-image.jpg',
width: 1200,
height: 630,
alt: 'Damjan Savic - AI & Automation Specialist',
},
],
},
twitter: {
card: 'summary_large_image',
creator: '@damjansavic',
},
};
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params;
if (!locales.includes(locale as Locale)) {
notFound();
}
setRequestLocale(locale);
const messages = await getMessages();
return (
<html lang={locale} className={inter.variable}>
<body className="bg-zinc-900 text-zinc-50 antialiased">
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}