+
+
+
-
{s.title}
-
{s.description}
+
{s.title}
+
{s.description}
+
+
+
-
-
-
{s.essential}
-
Always on
-
-
-
-
-
-
+
+
+
{s.essential}
+
Always on
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx
index fb26a7e..df2612b 100644
--- a/app/components/Contact.tsx
+++ b/app/components/Contact.tsx
@@ -21,11 +21,15 @@ const Contact = () => {
`/api/content/page?key=${encodeURIComponent("home-contact")}&locale=${encodeURIComponent(locale)}`,
);
const data = await res.json();
- if (data?.content?.content) {
+ // Only use CMS content if it exists for the active locale.
+ if (data?.content?.content && data?.content?.locale === locale) {
setCmsDoc(data.content.content as JSONContent);
+ } else {
+ setCmsDoc(null);
}
} catch {
// ignore; fallback to static
+ setCmsDoc(null);
}
})();
}, [locale]);
diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx
index 9284645..89d12f6 100644
--- a/app/components/Hero.tsx
+++ b/app/components/Hero.tsx
@@ -20,11 +20,17 @@ const Hero = () => {
`/api/content/page?key=${encodeURIComponent("home-hero")}&locale=${encodeURIComponent(locale)}`,
);
const data = await res.json();
- if (data?.content?.content) {
+ // Only use CMS content if it exists for the active locale.
+ // If the API falls back to another locale, keep showing next-intl strings
+ // so the locale switch visibly changes the page.
+ if (data?.content?.content && data?.content?.locale === locale) {
setCmsDoc(data.content.content as JSONContent);
+ } else {
+ setCmsDoc(null);
}
} catch {
// ignore; fallback to static
+ setCmsDoc(null);
}
})();
}, [locale]);
diff --git a/app/legal-notice/page.tsx b/app/legal-notice/page.tsx
index 02c11ae..877dce4 100644
--- a/app/legal-notice/page.tsx
+++ b/app/legal-notice/page.tsx
@@ -24,12 +24,18 @@ export default function LegalNotice() {
`/api/content/page?key=${encodeURIComponent("legal-notice")}&locale=${encodeURIComponent(locale)}`,
);
const data = await res.json();
- if (data?.content?.content) {
+ // Only use CMS content if it exists for the active locale.
+ if (data?.content?.content && data?.content?.locale === locale) {
setCmsDoc(data.content.content as JSONContent);
setCmsTitle((data.content.title as string | null) ?? null);
+ } else {
+ setCmsDoc(null);
+ setCmsTitle(null);
}
} catch {
// ignore; fallback to static content
+ setCmsDoc(null);
+ setCmsTitle(null);
}
})();
}, [locale]);
diff --git a/app/privacy-policy/page.tsx b/app/privacy-policy/page.tsx
index bc36637..e843b4f 100644
--- a/app/privacy-policy/page.tsx
+++ b/app/privacy-policy/page.tsx
@@ -24,12 +24,18 @@ export default function PrivacyPolicy() {
`/api/content/page?key=${encodeURIComponent("privacy-policy")}&locale=${encodeURIComponent(locale)}`,
);
const data = await res.json();
- if (data?.content?.content) {
+ // Only use CMS content if it exists for the active locale.
+ if (data?.content?.content && data?.content?.locale === locale) {
setCmsDoc(data.content.content as JSONContent);
setCmsTitle((data.content.title as string | null) ?? null);
+ } else {
+ setCmsDoc(null);
+ setCmsTitle(null);
}
} catch {
// ignore; fallback to static content
+ setCmsDoc(null);
+ setCmsTitle(null);
}
})();
}, [locale]);
diff --git a/next.config.ts b/next.config.ts
index a7bfbf2..4a20d7d 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -17,7 +17,9 @@ const nextConfig: NextConfig = {
poweredByHeader: false,
// React Strict Mode
- reactStrictMode: true,
+ // In dev, React StrictMode double-mount can cause visible animation flicker
+ // (Framer Motion "fade starts, disappears, then pops").
+ reactStrictMode: process.env.NODE_ENV === "production",
// Disable ESLint during build for Docker
eslint: {
diff --git a/prisma/seed.ts b/prisma/seed.ts
index fa8a4f2..7087fee 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -3,6 +3,18 @@ import { slugify } from "../lib/slug";
const prisma = new PrismaClient();
+function tiptapParagraph(text: string) {
+ return {
+ type: "doc",
+ content: [
+ {
+ type: "paragraph",
+ content: [{ type: "text", text }],
+ },
+ ],
+ };
+}
+
async function main() {
console.log("🌱 Seeding database...");
@@ -11,6 +23,104 @@ async function main() {
await prisma.pageView.deleteMany();
await prisma.project.deleteMany();
+ // Ensure base site settings & minimal localized CMS defaults (do NOT overwrite existing content).
+ await prisma.siteSettings.upsert({
+ where: { id: 1 },
+ update: {},
+ create: { id: 1, defaultLocale: "en", locales: ["en", "de"] },
+ });
+
+ async function ensureContentPage(
+ key: string,
+ translations: Array<{ locale: "en" | "de"; title: string; contentText: string }>,
+ ) {
+ const page = await prisma.contentPage.upsert({
+ where: { key },
+ update: {},
+ create: { key, status: "PUBLISHED" },
+ });
+
+ for (const tr of translations) {
+ await prisma.contentPageTranslation.upsert({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ where: { pageId_locale: { pageId: page.id, locale: tr.locale } } as any,
+ update: {},
+ create: {
+ pageId: page.id,
+ locale: tr.locale,
+ title: tr.title,
+ content: tiptapParagraph(tr.contentText),
+ },
+ });
+ }
+ }
+
+ await ensureContentPage("home-hero", [
+ {
+ locale: "en",
+ title: "Hero",
+ contentText: "I build fast, secure, self-hosted platforms — and I love clean UX.",
+ },
+ {
+ locale: "de",
+ title: "Hero",
+ contentText: "Ich baue schnelle, sichere, selbst gehostete Plattformen — mit sauberem UX.",
+ },
+ ]);
+
+ await ensureContentPage("home-about", [
+ {
+ locale: "en",
+ title: "About",
+ contentText: "I’m a software engineer focused on performance, security, and maintainable systems.",
+ },
+ {
+ locale: "de",
+ title: "Ăśber mich",
+ contentText: "Ich bin Software Engineer mit Fokus auf Performance, Security und wartbare Systeme.",
+ },
+ ]);
+
+ await ensureContentPage("home-contact", [
+ {
+ locale: "en",
+ title: "Contact",
+ contentText: "Want to work together? Send me a message and I’ll get back to you.",
+ },
+ {
+ locale: "de",
+ title: "Kontakt",
+ contentText: "Lust auf Zusammenarbeit? Schreib mir und ich melde mich zurĂĽck.",
+ },
+ ]);
+
+ // These are used by /[locale]/legal-notice and /[locale]/privacy-policy (re-exported pages)
+ await ensureContentPage("legal-notice", [
+ {
+ locale: "en",
+ title: "Legal notice",
+ contentText: "Legal notice content can be edited in the CMS per language.",
+ },
+ {
+ locale: "de",
+ title: "Impressum",
+ contentText: "Impressum-Inhalt kann im CMS pro Sprache bearbeitet werden.",
+ },
+ ]);
+
+ await ensureContentPage("privacy-policy", [
+ {
+ locale: "en",
+ title: "Privacy policy",
+ contentText: "Privacy policy content can be edited in the CMS per language.",
+ },
+ {
+ locale: "de",
+ title: "Datenschutzerklärung",
+ contentText: "Datenschutzerklärung kann im CMS pro Sprache bearbeitet werden.",
+ },
+ ]);
+
// Create real projects
const projects = [
{