diff --git a/next.config.ts b/next.config.ts index 1ab2804..0b5b740 100644 --- a/next.config.ts +++ b/next.config.ts @@ -53,8 +53,59 @@ const nextConfig: NextConfig = { }, ], }, + { + source: '/images/:path*', + headers: [ + { + key: 'Cache-Control', + value: 'public, max-age=31536000, immutable', + }, + ], + }, + { + source: '/_next/static/:path*', + headers: [ + { + key: 'Cache-Control', + value: 'public, max-age=31536000, immutable', + }, + ], + }, + // Content-Language Headers für jede Sprache (wichtig für Bing) + { + source: '/de/:path*', + headers: [ + { + key: 'Content-Language', + value: 'de-DE', + }, + ], + }, + { + source: '/en/:path*', + headers: [ + { + key: 'Content-Language', + value: 'en-US', + }, + ], + }, + { + source: '/sr/:path*', + headers: [ + { + key: 'Content-Language', + value: 'sr-RS', + }, + ], + }, ]; }, + + // Compiler-Optimierungen + compiler: { + removeConsole: process.env.NODE_ENV === 'production', + }, }; const withMDX = createMDX({ diff --git a/package.json b/package.json index 62fcd5d..6f3ec94 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,15 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint", + "lint": "next lint --strict", "build:images": "node scripts/optimize-images.js", + "generate:images": "node scripts/generate-blog-images.mjs", + "generate:images:dry": "node scripts/generate-blog-images.mjs --dry-run", "test": "vitest", "test:coverage": "vitest run --coverage" }, "dependencies": { + "openai": "^4.77.0", "@mdx-js/loader": "^3.1.0", "@mdx-js/mdx": "^3.1.0", "@mdx-js/react": "^3.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2648ef5..9cbbf5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: next-mdx-remote: specifier: ^5.0.0 version: 5.0.0(@types/react@19.2.8)(acorn@8.15.0)(react@19.2.3) + openai: + specifier: ^4.77.0 + version: 4.104.0(ws@8.18.3) react: specifier: ^19.0.0 version: 19.2.3 @@ -831,6 +834,12 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@22.19.7': resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==} @@ -1034,6 +1043,10 @@ packages: '@vitest/utils@1.6.1': resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1052,6 +1065,10 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1618,6 +1635,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -1685,10 +1706,17 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1826,6 +1854,9 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2341,6 +2372,20 @@ packages: sass: optional: true + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -2399,6 +2444,18 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + openai@4.104.0: + resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2987,6 +3044,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3121,6 +3181,10 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + web-vitals@5.1.0: resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==} @@ -3815,6 +3879,15 @@ snapshots: '@types/ms@2.1.0': {} + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 22.19.7 + form-data: 4.0.4 + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@22.19.7': dependencies: undici-types: 6.21.0 @@ -4020,6 +4093,10 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -4033,6 +4110,10 @@ snapshots: agent-base@7.1.4: optional: true + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4144,8 +4225,7 @@ snapshots: async-function@1.0.0: {} - asynckit@0.4.0: - optional: true + asynckit@0.4.0: {} autoprefixer@10.4.21(postcss@8.5.6): dependencies: @@ -4289,7 +4369,6 @@ snapshots: combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 - optional: true comma-separated-tokens@2.0.3: {} @@ -4378,8 +4457,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: - optional: true + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -4850,6 +4928,8 @@ snapshots: esutils@2.0.3: {} + event-target-shim@5.0.1: {} + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -4927,6 +5007,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data-encoder@1.7.2: {} + form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -4934,7 +5016,11 @@ snapshots: es-set-tostringtag: 2.1.0 hasown: 2.0.2 mime-types: 2.1.35 - optional: true + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 fraction.js@4.3.7: {} @@ -5120,6 +5206,10 @@ snapshots: human-signals@5.0.0: {} + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -5744,13 +5834,11 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: - optional: true + mime-db@1.52.0: {} mime-types@2.1.35: dependencies: mime-db: 1.52.0 - optional: true mimic-fn@4.0.0: {} @@ -5842,6 +5930,12 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.19: {} normalize-path@3.0.0: {} @@ -5903,6 +5997,20 @@ snapshots: dependencies: mimic-fn: 4.0.0 + openai@4.104.0(ws@8.18.3): + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + ws: 8.18.3 + transitivePeerDependencies: + - encoding + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -6648,6 +6756,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + undici-types@5.26.5: {} + undici-types@6.21.0: {} unified@11.0.5: @@ -6836,6 +6946,8 @@ snapshots: xml-name-validator: 5.0.0 optional: true + web-streams-polyfill@4.0.0-beta.3: {} + web-vitals@5.1.0: {} webidl-conversions@3.0.1: {} diff --git a/public/hero-portrait.jpg b/public/hero-portrait.jpg new file mode 100644 index 0000000..dea4e35 Binary files /dev/null and b/public/hero-portrait.jpg differ diff --git a/public/images/gallery/editorial-dark.webp b/public/images/gallery/editorial-dark.webp new file mode 100644 index 0000000..f86195f Binary files /dev/null and b/public/images/gallery/editorial-dark.webp differ diff --git a/public/images/gallery/editorial-side.webp b/public/images/gallery/editorial-side.webp new file mode 100644 index 0000000..326771b Binary files /dev/null and b/public/images/gallery/editorial-side.webp differ diff --git a/public/images/gallery/frontal-smile.webp b/public/images/gallery/frontal-smile.webp new file mode 100644 index 0000000..4539891 Binary files /dev/null and b/public/images/gallery/frontal-smile.webp differ diff --git a/public/images/gallery/gesturing-dark.webp b/public/images/gallery/gesturing-dark.webp new file mode 100644 index 0000000..4dd8412 Binary files /dev/null and b/public/images/gallery/gesturing-dark.webp differ diff --git a/public/images/gallery/handshake.webp b/public/images/gallery/handshake.webp new file mode 100644 index 0000000..b9c9017 Binary files /dev/null and b/public/images/gallery/handshake.webp differ diff --git a/public/images/gallery/meeting-boardroom.webp b/public/images/gallery/meeting-boardroom.webp new file mode 100644 index 0000000..7e3b5bc Binary files /dev/null and b/public/images/gallery/meeting-boardroom.webp differ diff --git a/public/images/gallery/over-shoulder-smile.webp b/public/images/gallery/over-shoulder-smile.webp new file mode 100644 index 0000000..95410c0 Binary files /dev/null and b/public/images/gallery/over-shoulder-smile.webp differ diff --git a/public/images/gallery/phone-skyline.webp b/public/images/gallery/phone-skyline.webp new file mode 100644 index 0000000..3602f9e Binary files /dev/null and b/public/images/gallery/phone-skyline.webp differ diff --git a/public/images/gallery/profile-white.webp b/public/images/gallery/profile-white.webp new file mode 100644 index 0000000..6bb473c Binary files /dev/null and b/public/images/gallery/profile-white.webp differ diff --git a/public/images/gallery/speaking-dark.webp b/public/images/gallery/speaking-dark.webp new file mode 100644 index 0000000..4e02fbb Binary files /dev/null and b/public/images/gallery/speaking-dark.webp differ diff --git a/public/images/gallery/studio-seated-white.webp b/public/images/gallery/studio-seated-white.webp new file mode 100644 index 0000000..5780d02 Binary files /dev/null and b/public/images/gallery/studio-seated-white.webp differ diff --git a/public/images/gallery/studio-seated.webp b/public/images/gallery/studio-seated.webp new file mode 100644 index 0000000..3ab5573 Binary files /dev/null and b/public/images/gallery/studio-seated.webp differ diff --git a/public/images/gallery/thinker-dark.webp b/public/images/gallery/thinker-dark.webp new file mode 100644 index 0000000..61e9e98 Binary files /dev/null and b/public/images/gallery/thinker-dark.webp differ diff --git a/public/images/gallery/video-call.webp b/public/images/gallery/video-call.webp new file mode 100644 index 0000000..abc882f Binary files /dev/null and b/public/images/gallery/video-call.webp differ diff --git a/public/images/gallery/walking-phone.webp b/public/images/gallery/walking-phone.webp new file mode 100644 index 0000000..2c6580b Binary files /dev/null and b/public/images/gallery/walking-phone.webp differ diff --git a/public/images/headshot.webp b/public/images/headshot.webp new file mode 100644 index 0000000..ae2c13e Binary files /dev/null and b/public/images/headshot.webp differ diff --git a/scripts/optimize-images.mjs b/scripts/optimize-images.mjs new file mode 100644 index 0000000..d52c044 --- /dev/null +++ b/scripts/optimize-images.mjs @@ -0,0 +1,53 @@ +import sharp from 'sharp'; +import { readdir, mkdir } from 'fs/promises'; +import path from 'path'; + +const SOURCE_DIR = './photos'; +const OUTPUT_DIR = './public/images/gallery'; + +const imageMapping = { + '0c477daf-538e-42b8-9171-3be0f32b052b.jpg': 'thinker-dark.webp', + '8f1df0fa-ff21-4d10-8f4e-67807405405d.jpg': 'studio-seated.webp', + '11da5e53-100b-406f-9ca7-1c84ff193a0a.jpg': 'profile-white.webp', + 'e1af9b8e-f857-4df3-8e05-8f51966677f9.jpg': 'editorial-dark.webp', + 'Gemini_Generated_Image_4y9h4z4y9h4z4y9h.png': 'walking-phone.webp', + 'Gemini_Generated_Image_6i8jtc6i8jtc6i8j.png': 'handshake.webp', + 'Gemini_Generated_Image_8x4pc88x4pc88x4p.png': 'speaking-dark.webp', + 'Gemini_Generated_Image_b79h3hb79h3hb79h.png': 'meeting-boardroom.webp', + 'Gemini_Generated_Image_bhiyhrbhiyhrbhiy.png': 'editorial-side.webp', + 'Gemini_Generated_Image_d7l8ihd7l8ihd7l8.png': 'over-shoulder-smile.webp', + 'Gemini_Generated_Image_dn15mrdn15mrdn15.png': 'frontal-smile.webp', + 'Gemini_Generated_Image_kri03okri03okri0.png': 'phone-skyline.webp', + 'Gemini_Generated_Image_lxg2aplxg2aplxg2.png': 'gesturing-dark.webp', + 'Gemini_Generated_Image_vz86wsvz86wsvz86.png': 'video-call.webp', + 'Gemini_Generated_Image_x4fuuix4fuuix4fu.png': 'studio-seated-white.webp', +}; + +async function optimizeImages() { + try { + await mkdir(OUTPUT_DIR, { recursive: true }); + + for (const [source, target] of Object.entries(imageMapping)) { + const sourcePath = path.join(SOURCE_DIR, source); + const targetPath = path.join(OUTPUT_DIR, target); + + console.log(`Processing: ${source} -> ${target}`); + + await sharp(sourcePath) + .resize(1200, null, { + withoutEnlargement: true, + fit: 'inside' + }) + .webp({ quality: 80 }) + .toFile(targetPath); + + console.log(`✓ Saved: ${target}`); + } + + console.log('\n✅ All images optimized successfully!'); + } catch (error) { + console.error('Error:', error); + } +} + +optimizeImages(); diff --git a/src/app/[locale]/about/page.tsx b/src/app/[locale]/about/page.tsx index ebcb716..d10cde8 100644 --- a/src/app/[locale]/about/page.tsx +++ b/src/app/[locale]/about/page.tsx @@ -1,19 +1,83 @@ import { setRequestLocale } from 'next-intl/server'; import type { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; -import Link from 'next/link'; +import { Hero, Skills, Workflow, Journey } from '@/components/about'; +import { ProfilePageJsonLd, BreadcrumbJsonLd } from '@/components/seo'; +import { type Locale } from '@/i18n/config'; type Props = { params: Promise<{ locale: string }>; }; +const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com'; + export async function generateMetadata({ params }: Props): Promise { const { locale } = await params; const t = await getTranslations({ locale, namespace: 'pages.about.seo' }); + const keywords: Record = { + de: [ + 'Über Damjan Savić', + 'KI Entwickler Köln', + 'AI Developer Deutschland', + 'Fullstack Entwickler', + 'Lebenslauf', + 'Skills', + 'Erfahrung', + 'Software Entwickler', + ], + en: [ + 'About Damjan Savić', + 'Remote AI Developer', + 'AI Developer Europe', + 'Fullstack Developer', + 'AI Consultant', + 'Resume', + 'Skills', + 'Experience', + 'Software Developer', + 'Hire AI Developer', + 'Freelance AI Developer USA', + 'Freelance AI Developer UK', + 'Remote Developer', + 'n8n Expert', + 'Voice AI Developer', + ], + sr: [ + 'O Damjanu Saviću', + 'AI Developer Keln', + 'AI Developer Nemačka', + 'Fullstack Developer', + 'Biografija', + 'Veštine', + 'Iskustvo', + 'Software Developer', + ], + }; + + const localePath = locale === 'de' ? '/about' : `/${locale}/about`; + return { title: t('title'), description: t('description'), + keywords: keywords[locale] || keywords.de, + alternates: { + canonical: `${BASE_URL}${localePath}`, + languages: { + 'x-default': `${BASE_URL}/about`, + de: `${BASE_URL}/about`, + en: `${BASE_URL}/en/about`, + sr: `${BASE_URL}/sr/about`, + }, + }, + openGraph: { + title: t('title'), + description: t('description'), + url: `${BASE_URL}${localePath}`, + type: 'profile', + locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US', + alternateLocale: ['de_DE', 'en_US', 'sr_RS'].filter(l => l !== (locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US')), + }, }; } @@ -23,67 +87,53 @@ export default async function AboutPage({ params }: Props) { const t = await getTranslations('pages.about'); + const breadcrumbItems = [ + { name: locale === 'de' ? 'Start' : locale === 'sr' ? 'Početna' : 'Home', url: `/${locale}` }, + { name: locale === 'de' ? 'Über mich' : locale === 'sr' ? 'O meni' : 'About', url: `/${locale}/about` }, + ]; + return ( -
-
- {/* Breadcrumb */} - +
+ + - {/* Hero Section */} -
-

- {t('hero.aboutMe')} -

-

- {t('hero.title')} -

-

- {t('hero.description')} -

-
+ {/* Hero Section */} +
+ +
- {/* Skills Section */} -
-

Core Expertise

-
-
-

AI & Automation

-

- GPT-4, Claude API, Voice AI, n8n, Process Automation -

-
-
-

Fullstack Development

-

- Python, TypeScript, React, Next.js, Node.js -

-
-
-

System Integration

-

- ERP Systems, APIs, Database Design, DevOps -

-
+ {/* Skills Section */} +
+
+
+

+ {t('skills.title')} +

+

+ {t('skills.description')} +

-
+ +
+
- {/* Contact CTA */} -
-

Interested in working together?

- - Get in Touch - -
-
-
+ {/* Workflow Section */} +
+
+
+

+ {t('workflow.title')} +

+

+ {t('workflow.description')} +

+
+ +
+
+ + {/* Journey Section */} + + ); } diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index 904cb9e..5dcc595 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -1,16 +1,19 @@ import { notFound } from 'next/navigation'; import { getTranslations, setRequestLocale } from 'next-intl/server'; -import { locales } from '@/i18n/config'; +import { locales, type Locale } from '@/i18n/config'; import Link from 'next/link'; import Image from 'next/image'; import type { Metadata } from 'next'; +import { BreadcrumbJsonLd, ArticleJsonLd } from '@/components/seo'; +import { getBlogPostBySlug, getBlogPostSlugs } from '@/lib/blog'; +import { renderMarkdown } from '@/lib/markdown'; type Props = { params: Promise<{ locale: string; slug: string }>; }; -// Blog post content -const blogPosts: Record> = { de: { + 'n8n-automatisierung-tutorial': { + title: 'n8n Tutorial: Workflows automatisieren ohne Code - Schritt für Schritt', + date: '2026-01-18', + excerpt: 'Lernen Sie, wie Sie mit n8n leistungsstarke Automatisierungen erstellen. Von der Installation bis zum ersten produktiven Workflow - inklusive Best Practices.', + category: 'Automatisierung', + tags: ['n8n', 'Tutorial', 'Automatisierung', 'Workflow', 'No-Code', 'Self-Hosted'], + coverImage: '/images/posts/n8n-automatisierung-tutorial/cover.jpg', + content: ` +## n8n Tutorial: Der vollständige Leitfaden für Workflow-Automatisierung + +n8n ist die führende Open-Source-Alternative zu Zapier und Make. In diesem umfassenden Tutorial zeige ich Ihnen alles, was Sie für den Einstieg brauchen. + +### Was ist n8n? + +n8n (gesprochen "n-eight-n") ist eine Open-Source-Workflow-Automatisierungsplattform, die es ermöglicht, verschiedene Anwendungen und Dienste miteinander zu verbinden. Der Name steht für "nodemation" - eine Kombination aus "node" und "automation". + +### Warum n8n wählen? + +Im Vergleich zu anderen Tools bietet n8n: +- **Self-Hosting**: Volle Kontrolle über Ihre Daten (DSGVO!) +- **Kostenlos**: Die Self-Hosted-Version ist komplett kostenlos +- **Fair-Code**: Open Source mit einer fairen Lizenz +- **300+ Integrationen**: Von CRM bis KI-APIs + +### Installation: 3 Wege zum Ziel + +#### Option 1: Docker (Empfohlen) + +\`\`\`bash +docker run -it --rm --name n8n -p 5678:5678 -v ~/.n8n:/home/node/.n8n n8nio/n8n +\`\`\` + +#### Option 2: NPM + +\`\`\`bash +npm install n8n -g +n8n start +\`\`\` + +#### Option 3: n8n Cloud + +Für einen schnellen Start ohne Infrastruktur-Aufwand können Sie n8n Cloud nutzen. + +### Ihr erster Workflow: E-Mail zu Slack + +Lassen Sie uns einen praktischen Workflow erstellen, der neue E-Mails automatisch in Slack postet: + +1. **Trigger hinzufügen**: Gmail Node als Trigger +2. **Transformation**: Function Node für Textformatierung +3. **Ausgabe**: Slack Node für die Nachricht + +### Best Practices für n8n + +Nach über 100 n8n-Projekten empfehle ich: + +- **Fehlerbehandlung**: Immer Error Workflow einrichten +- **Dokumentation**: Sticky Notes im Workflow nutzen +- **Versionierung**: Workflows regelmäßig exportieren +- **Monitoring**: Execution Log im Auge behalten + +### n8n mit KI kombinieren + +Die wahre Stärke von n8n zeigt sich in Kombination mit KI: + +1. Dokumente mit OpenAI analysieren +2. Automatische Zusammenfassungen generieren +3. Sentiment-Analyse für Kundenfeedback +4. Content-Generierung für Marketing + +### Fortgeschrittene Techniken + +Für Power-User: +- **Custom Nodes**: Eigene Integrationen entwickeln +- **Webhooks**: Echtzeit-Triggers von externen Systemen +- **Code Nodes**: JavaScript für komplexe Logik +- **Sub-Workflows**: Modularisierung für Wiederverwendbarkeit + +### Fazit + +n8n ist das mächtigste Automatisierungstool für Teams, die Wert auf Datenschutz und Flexibilität legen. Mit diesem Tutorial haben Sie das Fundament für professionelle Workflow-Automatisierung gelegt. + +**Nächster Schritt**: Starten Sie mit einem einfachen Use Case und erweitern Sie schrittweise. + `, + }, + 'geo-generative-engine-optimization': { + title: 'GEO: Generative Engine Optimization - SEO für die KI-Ära', + date: '2026-01-17', + excerpt: 'Wie Sie Ihre Inhalte für ChatGPT, Perplexity und andere KI-Suchmaschinen optimieren. Der neue Standard nach klassischem SEO.', + category: 'Marketing', + tags: ['GEO', 'SEO', 'KI', 'ChatGPT', 'Perplexity', 'Content Marketing', 'AEO'], + coverImage: '/images/posts/geo-generative-engine-optimization/cover.jpg', + content: ` +## GEO: Generative Engine Optimization - Der ultimative Guide + +Die Spielregeln für Online-Sichtbarkeit ändern sich grundlegend. Während SEO auf Google-Rankings abzielt, geht es bei GEO darum, von KI-Systemen wie ChatGPT, Perplexity und Google SGE zitiert zu werden. + +### Was ist Generative Engine Optimization (GEO)? + +GEO beschreibt die Optimierung von Inhalten, um von KI-gestützten Suchsystemen als Quelle verwendet zu werden. Im Gegensatz zu klassischem SEO geht es nicht um Rankings, sondern darum, als vertrauenswürdige Quelle für KI-generierte Antworten zu gelten. + +### Warum GEO jetzt wichtig wird + +Die Zahlen sprechen für sich: +- **30% weniger Google-Klicks** seit ChatGPT-Launch +- **50% der Gen-Z** nutzt TikTok und KI statt Google +- **Perplexity** verzeichnet 10 Mio. monatliche Nutzer + +### Die 7 Säulen der GEO-Optimierung + +#### 1. Autorität aufbauen (E-E-A-T) + +KI-Systeme bevorzugen Quellen mit nachweisbarer Expertise: +- Autor-Biografie mit Qualifikationen +- Zitate von Experten +- Verlinkung zu primären Quellen +- Regelmäßige Aktualisierungen + +#### 2. Strukturierte Daten nutzen + +Schema.org-Markup hilft KI, Ihre Inhalte zu verstehen: +- Article Schema mit Autor +- FAQ Schema für Fragen +- HowTo Schema für Anleitungen +- Organization Schema + +#### 3. Direkte Antworten liefern + +KI-Systeme lieben präzise, zitierbare Aussagen: +- Definitionen am Anfang von Abschnitten +- Bullet Points für Listen +- Zahlen und Statistiken +- Klare Wenn-Dann-Aussagen + +#### 4. Semantische Tiefe + +Behandeln Sie Themen umfassend: +- Alle relevanten Unterthemen abdecken +- Häufige Fragen beantworten +- Verschiedene Perspektiven zeigen +- Praxisbeispiele einbinden + +#### 5. Unique Content + +KI bevorzugt originelle Inhalte: +- Eigene Studien und Daten +- Fallstudien aus der Praxis +- Experteninterviews +- Proprietäre Frameworks + +#### 6. Technische Optimierung + +Stellen Sie sicher, dass KI-Crawler Ihre Inhalte lesen können: +- Robots.txt für GPTBot erlauben +- Schnelle Ladezeiten +- Mobile-First Design +- Saubere HTML-Struktur + +#### 7. Multi-Format-Strategie + +Verbreiten Sie Expertise auf verschiedenen Kanälen: +- Blog-Artikel (Google) +- YouTube-Videos (Perplexity) +- Podcast (Audio-KI) +- Social Media (Training-Daten) + +### GEO vs. SEO: Die Unterschiede + +| Aspekt | SEO | GEO | +|--------|-----|-----| +| Ziel | Ranking | Zitation | +| Metriken | Position, CTR | Mentions, Citations | +| Keywords | Exakt Match | Semantisch | +| Format | Optimiert für Nutzer | Optimiert für KI-Parsing | + +### Praktische GEO-Checkliste + +Für jeden Artikel prüfen: + +1. ✅ Klare Definition im ersten Absatz? +2. ✅ Strukturierte Daten implementiert? +3. ✅ Zahlen und Fakten mit Quellen? +4. ✅ FAQ-Sektion vorhanden? +5. ✅ Autor-Expertise erkennbar? +6. ✅ Aktualitätsdatum sichtbar? + +### Fazit: Die Zukunft ist GEO + +GEO ist kein Ersatz für SEO, sondern eine Erweiterung. Wer jetzt in GEO investiert, wird in 2-3 Jahren einen erheblichen Wettbewerbsvorteil haben, da KI-Suche klassische Suchmaschinen zunehmend ergänzt oder ersetzt. + +**Mein Rat**: Beginnen Sie heute mit der Optimierung Ihrer wichtigsten Inhalte nach GEO-Prinzipien. + `, + }, + 'gpt-api-integration-praxisguide': { + title: 'GPT API Integration: Vom API-Key zur produktionsreifen Anwendung', + date: '2026-01-16', + excerpt: 'Praktische Anleitung zur Integration von OpenAI GPT-4 in Ihre Anwendungen. Token-Optimierung, Fehlerbehandlung und Best Practices.', + category: 'KI-Entwicklung', + tags: ['GPT-4', 'OpenAI', 'API', 'Integration', 'Python', 'LLM', 'Prompt Engineering'], + coverImage: '/images/posts/gpt-api-integration-praxisguide/cover.jpg', + content: ` +## GPT API Integration: Der Praxisguide + +Die OpenAI API ist das Tor zu leistungsstarken KI-Funktionen in Ihren Anwendungen. In diesem Guide zeige ich Ihnen, wie Sie die Integration professionell und kostenbewusst umsetzen. + +### Erste Schritte mit der OpenAI API + +#### API-Key erstellen + +1. Account auf platform.openai.com erstellen +2. API Keys im Dashboard generieren +3. Sicher speichern (nie im Code!) + +#### Python-Setup + +\`\`\`python +import openai +from dotenv import load_dotenv +import os + +load_dotenv() +client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY")) +\`\`\` + +### Die verschiedenen Modelle + +Wählen Sie das richtige Modell für Ihren Use Case: + +| Modell | Stärken | Kosten | +|--------|---------|--------| +| GPT-4o | Multimodal, schnell | $5/1M tokens | +| GPT-4 | Beste Qualität | $30/1M tokens | +| GPT-3.5 | Schnell, günstig | $0.5/1M tokens | +| GPT-4o-mini | Ideal für Produktion | $0.15/1M tokens | + +### Token-Optimierung: Kosten senken + +Tokens sind die Grundlage der Abrechnung. So optimieren Sie: + +#### 1. Prompt-Engineering + +- Präzise Anweisungen +- Beispiele statt Erklärungen +- System-Prompts wiederverwenden + +#### 2. Response-Limits + +\`\`\`python +response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": prompt}], + max_tokens=500, # Limit setzen + temperature=0.7 +) +\`\`\` + +#### 3. Caching implementieren + +Für wiederkehrende Anfragen: + +\`\`\`python +import hashlib +from functools import lru_cache + +@lru_cache(maxsize=1000) +def cached_completion(prompt_hash): + # API-Aufruf nur bei Cache-Miss + pass +\`\`\` + +### Fehlerbehandlung für Produktion + +Robuste Fehlerbehandlung ist essentiell: + +\`\`\`python +import time +from openai import RateLimitError, APIError + +def robust_completion(prompt, retries=3): + for attempt in range(retries): + try: + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": prompt}] + ) + return response.choices[0].message.content + except RateLimitError: + wait_time = 2 ** attempt + time.sleep(wait_time) + except APIError as e: + if attempt == retries - 1: + raise + return None +\`\`\` + +### Streaming für bessere UX + +Für Chat-Anwendungen ist Streaming wichtig: + +\`\`\`python +stream = client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": prompt}], + stream=True +) + +for chunk in stream: + if chunk.choices[0].delta.content: + print(chunk.choices[0].delta.content, end="") +\`\`\` + +### Function Calling: GPT mit Tools verbinden + +Das mächtigste Feature für Agenten: + +\`\`\`python +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Wetter für Stadt abrufen", + "parameters": { + "type": "object", + "properties": { + "city": {"type": "string"} + }, + "required": ["city"] + } + } + } +] +\`\`\` + +### Sicherheit und DSGVO + +Wichtige Aspekte für den produktiven Einsatz: + +- **Keine sensiblen Daten** in Prompts +- **Audit-Logging** für Compliance +- **Data Processing Agreement** mit OpenAI +- **Opt-out** aus Training-Nutzung + +### Monitoring und Kosten-Tracking + +Behalten Sie die Kosten im Blick: + +\`\`\`python +def track_usage(response): + usage = response.usage + cost = (usage.prompt_tokens * 0.00003) + + (usage.completion_tokens * 0.00006) + log_to_database(cost, usage) +\`\`\` + +### Fazit + +Die GPT API bietet enorme Möglichkeiten, erfordert aber sorgfältige Planung. Mit den hier vorgestellten Best Practices sind Sie für den produktiven Einsatz gerüstet. + +**Nächster Schritt**: Starten Sie mit einem kleinen Proof-of-Concept und skalieren Sie schrittweise. + `, + }, + 'ki-agenten-unternehmen-guide': { + title: 'KI-Agenten für Unternehmen: Der ultimative Praxisguide 2026', + date: '2026-01-15', + excerpt: 'Wie Unternehmen KI-Agenten einsetzen können, um Prozesse zu automatisieren, Kosten zu senken und Wettbewerbsvorteile zu gewinnen.', + category: 'KI-Entwicklung', + tags: ['KI-Agenten', 'Automatisierung', 'GPT-4', 'Claude', 'LLM', 'Unternehmen'], + coverImage: '/images/posts/ki-agenten-unternehmen-guide/cover.jpg', + content: ` +## KI-Agenten für Unternehmen: Der ultimative Praxisguide 2026 + +KI-Agenten revolutionieren die Art und Weise, wie Unternehmen arbeiten. In diesem umfassenden Guide zeige ich Ihnen, wie Sie KI-Agenten erfolgreich in Ihrem Unternehmen einsetzen können. + +### Was sind KI-Agenten? + +KI-Agenten sind autonome Softwaresysteme, die auf Basis von Large Language Models (LLMs) wie GPT-4 oder Claude komplexe Aufgaben eigenständig ausführen können. Im Gegensatz zu einfachen Chatbots können sie: +- **Mehrstufige Aufgaben** selbstständig planen und ausführen +- **Tools und APIs** nutzen, um mit externen Systemen zu interagieren +- **Kontextbezogene Entscheidungen** treffen + +### Anwendungsfälle für KI-Agenten + +Die häufigsten Einsatzbereiche in Unternehmen sind: +1. Kundenservice-Automatisierung mit 24/7 Verfügbarkeit +2. Dokumentenanalyse und Datenextraktion +3. Lead-Qualifizierung und Vertriebsunterstützung +4. Code-Review und Entwicklungsassistenz + +### Technologie-Stack für KI-Agenten + +Für die Entwicklung von KI-Agenten empfehle ich folgende Technologien: +- **LLM-Anbieter**: OpenAI (GPT-4), Anthropic (Claude), oder Open-Source (Llama) +- **Frameworks**: LangChain, LlamaIndex, AutoGen +- **Vektor-Datenbanken**: Pinecone, Weaviate, Chroma + +### ROI und Kostenbetrachtung + +Die Investition in KI-Agenten zahlt sich in der Regel innerhalb von 6-12 Monaten aus. Typische Einsparungen: +- **60-80% Zeitersparnis** bei repetitiven Aufgaben +- **Verbesserte Qualität** durch konsistente Ausführung +- **Skalierbarkeit** ohne proportionale Kostensteigerung + +### Fazit + +KI-Agenten sind keine Zukunftsmusik mehr, sondern bereits heute einsatzbereit. Der Schlüssel zum Erfolg liegt in der richtigen Auswahl der Anwendungsfälle und einer schrittweisen Implementierung. + `, + }, + 'n8n-make-zapier-vergleich': { + title: 'n8n vs Make vs Zapier: Welches Automatisierungstool passt zu Ihnen?', + date: '2026-01-10', + excerpt: 'Ein detaillierter Vergleich der führenden Workflow-Automatisierungstools mit Vor- und Nachteilen für verschiedene Anwendungsfälle.', + category: 'Automatisierung', + tags: ['n8n', 'Make', 'Zapier', 'Workflow', 'Automatisierung', 'No-Code'], + coverImage: '/images/posts/n8n-make-zapier-vergleich/cover.jpg', + content: ` +## n8n vs Make vs Zapier: Der ultimative Vergleich + +Die Wahl des richtigen Automatisierungstools kann über den Erfolg Ihrer Digitalisierungsstrategie entscheiden. In diesem Artikel vergleiche ich die drei führenden Plattformen. + +### Zapier - Der Platzhirsch + +Zapier ist mit über 7.000 Integrationen der Marktführer für einfache Automatisierungen. +- **Vorteile**: Riesige App-Bibliothek, sehr einfach zu bedienen +- **Nachteile**: Teuer bei hohem Volumen, begrenzte Komplexität +- **Preis**: Ab $19.99/Monat für 750 Tasks + +### Make (ehemals Integromat) - Der Allrounder + +Make bietet eine perfekte Balance zwischen Benutzerfreundlichkeit und Leistung. +- **Vorteile**: Visuelle Oberfläche, faire Preise, gute Datenverarbeitung +- **Nachteile**: Lernkurve steiler als Zapier +- **Preis**: Ab €9/Monat für 10.000 Operationen + +### n8n - Die Open-Source-Alternative + +n8n ist ideal für technisch versierte Teams, die maximale Kontrolle wollen. +- **Vorteile**: Self-Hosting möglich, unbegrenzte Workflows, Open Source +- **Nachteile**: Erfordert technisches Know-how +- **Preis**: Self-Hosted kostenlos, Cloud ab €20/Monat + +### Meine Empfehlung + +Die Wahl hängt von Ihren Anforderungen ab: +1. **Zapier** für Einsteiger und einfache Automatisierungen +2. **Make** für mittelständische Unternehmen mit komplexeren Anforderungen +3. **n8n** für technische Teams mit Datenschutzanforderungen + +### Fazit + +Es gibt kein "bestes" Tool - nur das beste Tool für Ihren Anwendungsfall. Testen Sie alle drei mit kostenlosen Tarifen, bevor Sie sich entscheiden. + `, + }, + 'voice-ai-kundenservice-trends': { + title: 'Voice AI im Kundenservice: Trends und Best Practices 2026', + date: '2026-01-05', + excerpt: 'Wie Voice AI den Kundenservice revolutioniert und welche Technologien Sie für Ihr Unternehmen evaluieren sollten.', + category: 'Voice AI', + tags: ['Voice AI', 'Sprachassistent', 'Kundenservice', 'NLP', 'Chatbot', 'Vapi'], + coverImage: '/images/posts/voice-ai-kundenservice-trends/cover.jpg', + content: ` +## Voice AI im Kundenservice: Die Zukunft ist jetzt + +2026 markiert einen Wendepunkt für Voice AI im Kundenservice. Laut Gartner werden bis Ende des Jahres 30% aller Kundeninteraktionen von KI-gesteuerten Sprachassistenten abgewickelt. + +### Warum Voice AI jetzt durchstartet + +Mehrere Faktoren treiben die Adoption: +- **Verbesserte Sprachqualität**: Moderne TTS-Systeme wie ElevenLabs sind kaum von menschlichen Stimmen zu unterscheiden +- **Niedrigere Kosten**: Die Kosten pro Minute sind um 70% gesunken +- **Höhere Akzeptanz**: 64% der Kunden vertrauen empathischen KI-Agenten + +### Technologie-Landschaft 2026 + +Die führenden Plattformen für Voice AI: +1. **Vapi** - Spezialisiert auf Telefon-Bots mit niedriger Latenz +2. **Twilio** - Enterprise-Lösung mit voller Kontrolle +3. **ElevenLabs** - Beste Sprachqualität für Text-to-Speech +4. **Whisper** - Open-Source für Speech-to-Text + +### Implementierungsstrategien + +Für einen erfolgreichen Start empfehle ich: +- **Klein anfangen**: Starten Sie mit einem spezifischen Use Case +- **Hybridmodell**: Nahtlose Übergabe an menschliche Agenten bei Bedarf +- **Kontinuierliches Training**: Verbessern Sie die Erkennung basierend auf realen Gesprächen + +### ROI-Berechnung + +Typische Ergebnisse nach 6 Monaten: +- **45% Kostenreduktion** im Kundenservice +- **24/7 Verfügbarkeit** ohne Mehrkosten +- **15% höhere Kundenzufriedenheit** durch kürzere Wartezeiten + +### Fazit + +Voice AI ist reif für den produktiven Einsatz. Unternehmen, die jetzt investieren, sichern sich einen entscheidenden Wettbewerbsvorteil. + `, + }, + 'saas-mvp-entwicklung-4-wochen': { + title: 'SaaS MVP in 4 Wochen: Von der Idee zum Launch', + date: '2025-12-20', + excerpt: 'Schritt-für-Schritt Anleitung zur schnellen Entwicklung eines SaaS MVP mit Next.js, Prisma und modernen Cloud-Diensten.', + category: 'SaaS-Entwicklung', + tags: ['SaaS', 'MVP', 'Next.js', 'Startup', 'Produktentwicklung', 'React'], + coverImage: '/images/posts/saas-mvp-entwicklung-4-wochen/cover.jpg', + content: ` +## SaaS MVP in 4 Wochen: Der Praxisleitfaden + +Ein MVP (Minimum Viable Product) ist der schnellste Weg, Ihre SaaS-Idee am Markt zu validieren. In diesem Artikel zeige ich Ihnen, wie Sie in nur 4 Wochen ein funktionsfähiges Produkt entwickeln. + +### Der optimale Tech-Stack 2026 + +Nach über 20 SaaS-Projekten empfehle ich: +- **Frontend**: Next.js 15 mit App Router +- **Backend**: Next.js API Routes oder FastAPI +- **Datenbank**: PostgreSQL mit Prisma ORM +- **Auth**: NextAuth.js oder Clerk +- **Hosting**: Vercel oder Railway +- **Payments**: Stripe + +### Woche 1: Foundation + +In der ersten Woche legen wir das Fundament: +1. Projekt-Setup mit Next.js und TypeScript +2. Datenbank-Schema mit Prisma definieren +3. Authentication implementieren +4. Basis-UI mit Tailwind CSS + +### Woche 2: Core Features + +Fokus auf die Kernfunktionalität: +1. Haupt-Feature implementieren +2. API-Endpunkte entwickeln +3. Datenbankoperationen (CRUD) +4. Error Handling + +### Woche 3: Polish & Integration + +Verfeinerung und Integrationen: +1. Stripe Payment Integration +2. E-Mail-Benachrichtigungen +3. Dashboard und Analytics +4. Responsive Design + +### Woche 4: Launch Preparation + +Vorbereitung auf den Launch: +1. Testing und Bug-Fixes +2. SEO-Optimierung +3. Monitoring einrichten +4. Dokumentation erstellen + +### Kosten-Überblick + +Typische monatliche Kosten für ein MVP: +- **Vercel**: $0-20 (Hobby bis Pro) +- **Database**: $0-25 (Supabase/Neon) +- **Domain**: ~$12/Jahr +- **Stripe**: 2.9% + 30¢ pro Transaktion + +### Fazit + +Mit dem richtigen Stack und fokussierter Entwicklung ist ein SaaS MVP in 4 Wochen absolut realistisch. Der Schlüssel ist, sich auf die Kernfunktionalität zu konzentrieren und alles andere für spätere Iterationen aufzuheben. + `, + }, 'erp-integration-breuninger': { title: 'ApparelMagic und TradeByte: Analyse komplexer Integrationen', date: '2024-02-09', @@ -30,13 +598,13 @@ const blogPosts: Record - slugs.map((slug) => ({ + allSlugs.map((slug) => ({ locale, slug, })) ); } +const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com'; + +// Helper to get post from either legacy or dynamic source +function getPost(locale: string, slug: string) { + // First check legacy posts (they have translations) + const legacyPost = legacyBlogPosts[locale]?.[slug] || legacyBlogPosts.de[slug]; + if (legacyPost) return legacyPost; + + // Fall back to dynamic posts (German only for now) + return getBlogPostBySlug(slug); +} + export async function generateMetadata({ params }: Props): Promise { const { locale, slug } = await params; - const post = blogPosts[locale]?.[slug] || blogPosts.de[slug]; + const post = getPost(locale, slug); if (!post) { return { @@ -156,12 +1260,25 @@ export async function generateMetadata({ params }: Props): Promise { return { title: post.title, description: post.excerpt, + keywords: post.tags, + alternates: { + canonical: `${BASE_URL}/${locale}/blog/${slug}`, + languages: { + 'x-default': `${BASE_URL}/de/blog/${slug}`, + de: `${BASE_URL}/de/blog/${slug}`, + en: `${BASE_URL}/en/blog/${slug}`, + sr: `${BASE_URL}/sr/blog/${slug}`, + }, + }, openGraph: { title: post.title, description: post.excerpt, type: 'article', publishedTime: post.date, tags: post.tags, + locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US', + alternateLocale: ['de_DE', 'en_US', 'sr_RS'].filter(l => l !== (locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US')), + url: `${BASE_URL}/${locale}/blog/${slug}`, }, }; } @@ -171,7 +1288,7 @@ export default async function BlogPostPage({ params }: Props) { setRequestLocale(locale); - const post = blogPosts[locale]?.[slug] || blogPosts.de[slug]; + const post = getPost(locale, slug); const t = await getTranslations('blog'); if (!post) { @@ -192,8 +1309,24 @@ export default async function BlogPostPage({ params }: Props) { }); }; + const breadcrumbItems = [ + { name: 'Home', url: `/${locale}` }, + { name: 'Blog', url: `/${locale}/blog` }, + { name: post.title, url: `/${locale}/blog/${slug}` }, + ]; + return (
+ +
{/* Back Link */} {/* Content */} -
- {post.content.split('\n').map((paragraph, index) => { - if (paragraph.startsWith('## ')) { - return

{paragraph.replace('## ', '')}

; - } - if (paragraph.startsWith('### ')) { - return

{paragraph.replace('### ', '')}

; - } - if (paragraph.startsWith('- **')) { - return
  • {paragraph.replace('- ', '')}
  • ; - } - if (paragraph.startsWith('1. ') || paragraph.startsWith('2. ') || paragraph.startsWith('3. ') || paragraph.startsWith('4. ')) { - return
  • {paragraph.replace(/^\d+\. /, '')}
  • ; - } - if (paragraph.trim()) { - return

    {paragraph}

    ; - } - return null; - })} -
    + {renderMarkdown(post.content)}
    ); diff --git a/src/app/[locale]/blog/page.tsx b/src/app/[locale]/blog/page.tsx index 9b6fa5f..1b33fbc 100644 --- a/src/app/[locale]/blog/page.tsx +++ b/src/app/[locale]/blog/page.tsx @@ -1,25 +1,84 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import { motion } from 'framer-motion'; -import { ArrowRight, Loader2, Calendar, Tag, ExternalLink } from 'lucide-react'; -import { useTranslations, useLocale } from 'next-intl'; +import { setRequestLocale } from 'next-intl/server'; +import { getTranslations } from 'next-intl/server'; +import { ArrowRight, Calendar, Tag, ExternalLink, ChevronLeft, ChevronRight } from 'lucide-react'; import Link from 'next/link'; import Image from 'next/image'; +import type { Metadata } from 'next'; +import { getAllBlogPosts, BlogPost } from '@/lib/blog'; -interface BlogPost { - slug: string; - title: string; - date: string; - excerpt: string; - coverImage: string; - category?: string; - tags?: string[]; -} +const POSTS_PER_PAGE = 12; -// Blog posts data - in a real app, this would come from a CMS or MDX files -const blogPostsData: Record = { +type Props = { + params: Promise<{ locale: string }>; + searchParams: Promise<{ page?: string }>; +}; + +// Legacy blog posts with translations (these have manual translations) +const legacyBlogPosts: Record = { de: [ + { + slug: 'n8n-automatisierung-tutorial', + title: 'n8n Tutorial: Workflows automatisieren ohne Code - Schritt für Schritt', + date: '2026-01-18', + excerpt: 'Lernen Sie, wie Sie mit n8n leistungsstarke Automatisierungen erstellen. Von der Installation bis zum ersten produktiven Workflow - inklusive Best Practices.', + category: 'Automatisierung', + tags: ['n8n', 'Tutorial', 'Automatisierung', 'Workflow', 'No-Code', 'Self-Hosted'], + coverImage: '/images/posts/n8n-automatisierung-tutorial/cover.jpg', + }, + { + slug: 'geo-generative-engine-optimization', + title: 'GEO: Generative Engine Optimization - SEO für die KI-Ära', + date: '2026-01-17', + excerpt: 'Wie Sie Ihre Inhalte für ChatGPT, Perplexity und andere KI-Suchmaschinen optimieren. Der neue Standard nach klassischem SEO.', + category: 'Marketing', + tags: ['GEO', 'SEO', 'KI', 'ChatGPT', 'Perplexity', 'Content Marketing', 'AEO'], + coverImage: '/images/posts/geo-generative-engine-optimization/cover.jpg', + }, + { + slug: 'gpt-api-integration-praxisguide', + title: 'GPT API Integration: Vom API-Key zur produktionsreifen Anwendung', + date: '2026-01-16', + excerpt: 'Praktische Anleitung zur Integration von OpenAI GPT-4 in Ihre Anwendungen. Token-Optimierung, Fehlerbehandlung und Best Practices.', + category: 'KI-Entwicklung', + tags: ['GPT-4', 'OpenAI', 'API', 'Integration', 'Python', 'LLM', 'Prompt Engineering'], + coverImage: '/images/posts/gpt-api-integration-praxisguide/cover.jpg', + }, + { + slug: 'ki-agenten-unternehmen-guide', + title: 'KI-Agenten für Unternehmen: Der ultimative Praxisguide 2026', + date: '2026-01-15', + excerpt: 'Wie Unternehmen KI-Agenten einsetzen können, um Prozesse zu automatisieren, Kosten zu senken und Wettbewerbsvorteile zu gewinnen.', + category: 'KI-Entwicklung', + tags: ['KI-Agenten', 'Automatisierung', 'GPT-4', 'Claude', 'LLM', 'Unternehmen'], + coverImage: '/images/posts/ki-agenten-unternehmen-guide/cover.jpg', + }, + { + slug: 'n8n-make-zapier-vergleich', + title: 'n8n vs Make vs Zapier: Welches Automatisierungstool passt zu Ihnen?', + date: '2026-01-10', + excerpt: 'Ein detaillierter Vergleich der führenden Workflow-Automatisierungstools mit Vor- und Nachteilen für verschiedene Anwendungsfälle.', + category: 'Automatisierung', + tags: ['n8n', 'Make', 'Zapier', 'Workflow', 'Automatisierung', 'No-Code'], + coverImage: '/images/posts/n8n-make-zapier-vergleich/cover.jpg', + }, + { + slug: 'voice-ai-kundenservice-trends', + title: 'Voice AI im Kundenservice: Trends und Best Practices 2026', + date: '2026-01-05', + excerpt: 'Wie Voice AI den Kundenservice revolutioniert und welche Technologien Sie für Ihr Unternehmen evaluieren sollten.', + category: 'Voice AI', + tags: ['Voice AI', 'Sprachassistent', 'Kundenservice', 'NLP', 'Chatbot', 'Vapi'], + coverImage: '/images/posts/voice-ai-kundenservice-trends/cover.jpg', + }, + { + slug: 'saas-mvp-entwicklung-4-wochen', + title: 'SaaS MVP in 4 Wochen: Von der Idee zum Launch', + date: '2025-12-20', + excerpt: 'Schritt-für-Schritt Anleitung zur schnellen Entwicklung eines SaaS MVP mit Next.js, Prisma und modernen Cloud-Diensten.', + category: 'SaaS-Entwicklung', + tags: ['SaaS', 'MVP', 'Next.js', 'Startup', 'Produktentwicklung', 'React'], + coverImage: '/images/posts/saas-mvp-entwicklung-4-wochen/cover.jpg', + }, { slug: 'erp-integration-breuninger', title: 'ApparelMagic und TradeByte: Analyse komplexer Integrationen', @@ -31,6 +90,69 @@ const blogPostsData: Record = { }, ], en: [ + { + slug: 'n8n-automatisierung-tutorial', + title: 'n8n Tutorial: Automate Workflows Without Code - Step by Step', + date: '2026-01-18', + excerpt: 'Learn how to create powerful automations with n8n. From installation to your first production workflow - including best practices.', + category: 'Automation', + tags: ['n8n', 'Tutorial', 'Automation', 'Workflow', 'No-Code', 'Self-Hosted'], + coverImage: '/images/posts/n8n-automatisierung-tutorial/cover.jpg', + }, + { + slug: 'geo-generative-engine-optimization', + title: 'GEO: Generative Engine Optimization - SEO for the AI Era', + date: '2026-01-17', + excerpt: 'How to optimize your content for ChatGPT, Perplexity and other AI search engines. The new standard after traditional SEO.', + category: 'Marketing', + tags: ['GEO', 'SEO', 'AI', 'ChatGPT', 'Perplexity', 'Content Marketing', 'AEO'], + coverImage: '/images/posts/geo-generative-engine-optimization/cover.jpg', + }, + { + slug: 'gpt-api-integration-praxisguide', + title: 'GPT API Integration: From API Key to Production-Ready Application', + date: '2026-01-16', + excerpt: 'Practical guide to integrating OpenAI GPT-4 into your applications. Token optimization, error handling and best practices.', + category: 'AI Development', + tags: ['GPT-4', 'OpenAI', 'API', 'Integration', 'Python', 'LLM', 'Prompt Engineering'], + coverImage: '/images/posts/gpt-api-integration-praxisguide/cover.jpg', + }, + { + slug: 'ki-agenten-unternehmen-guide', + title: 'AI Agents for Business: The Ultimate Practical Guide 2026', + date: '2026-01-15', + excerpt: 'How companies can use AI agents to automate processes, reduce costs and gain competitive advantages.', + category: 'AI Development', + tags: ['AI Agents', 'Automation', 'GPT-4', 'Claude', 'LLM', 'Enterprise'], + coverImage: '/images/posts/ki-agenten-unternehmen-guide/cover.jpg', + }, + { + slug: 'n8n-make-zapier-vergleich', + title: 'n8n vs Make vs Zapier: Which Automation Tool is Right for You?', + date: '2026-01-10', + excerpt: 'A detailed comparison of leading workflow automation tools with pros and cons for different use cases.', + category: 'Automation', + tags: ['n8n', 'Make', 'Zapier', 'Workflow', 'Automation', 'No-Code'], + coverImage: '/images/posts/n8n-make-zapier-vergleich/cover.jpg', + }, + { + slug: 'voice-ai-kundenservice-trends', + title: 'Voice AI in Customer Service: Trends and Best Practices 2026', + date: '2026-01-05', + excerpt: 'How Voice AI is revolutionizing customer service and which technologies you should evaluate for your business.', + category: 'Voice AI', + tags: ['Voice AI', 'Voice Assistant', 'Customer Service', 'NLP', 'Chatbot', 'Vapi'], + coverImage: '/images/posts/voice-ai-kundenservice-trends/cover.jpg', + }, + { + slug: 'saas-mvp-entwicklung-4-wochen', + title: 'SaaS MVP in 4 Weeks: From Idea to Launch', + date: '2025-12-20', + excerpt: 'Step-by-step guide to quickly developing a SaaS MVP with Next.js, Prisma and modern cloud services.', + category: 'SaaS Development', + tags: ['SaaS', 'MVP', 'Next.js', 'Startup', 'Product Development', 'React'], + coverImage: '/images/posts/saas-mvp-entwicklung-4-wochen/cover.jpg', + }, { slug: 'erp-integration-breuninger', title: 'ApparelMagic and TradeByte: Complex Integration Analysis', @@ -42,6 +164,69 @@ const blogPostsData: Record = { }, ], sr: [ + { + slug: 'n8n-automatisierung-tutorial', + title: 'n8n Tutorial: Automatizujte Radne Tokove Bez Koda - Korak po Korak', + date: '2026-01-18', + excerpt: 'Naučite kako da kreirate moćne automatizacije sa n8n. Od instalacije do prvog produktivnog radnog toka - uključujući najbolje prakse.', + category: 'Automatizacija', + tags: ['n8n', 'Tutorial', 'Automatizacija', 'Workflow', 'No-Code', 'Self-Hosted'], + coverImage: '/images/posts/n8n-automatisierung-tutorial/cover.jpg', + }, + { + slug: 'geo-generative-engine-optimization', + title: 'GEO: Generative Engine Optimization - SEO za AI Eru', + date: '2026-01-17', + excerpt: 'Kako da optimizujete sadržaj za ChatGPT, Perplexity i druge AI pretraživače. Novi standard posle klasičnog SEO-a.', + category: 'Marketing', + tags: ['GEO', 'SEO', 'AI', 'ChatGPT', 'Perplexity', 'Content Marketing', 'AEO'], + coverImage: '/images/posts/geo-generative-engine-optimization/cover.jpg', + }, + { + slug: 'gpt-api-integration-praxisguide', + title: 'GPT API Integracija: Od API Ključa do Produkcione Aplikacije', + date: '2026-01-16', + excerpt: 'Praktični vodič za integraciju OpenAI GPT-4 u vaše aplikacije. Optimizacija tokena, rukovanje greškama i najbolje prakse.', + category: 'AI Razvoj', + tags: ['GPT-4', 'OpenAI', 'API', 'Integracija', 'Python', 'LLM', 'Prompt Engineering'], + coverImage: '/images/posts/gpt-api-integration-praxisguide/cover.jpg', + }, + { + slug: 'ki-agenten-unternehmen-guide', + title: 'AI Agenti za Preduzeća: Ultimativni Praktični Vodič 2026', + date: '2026-01-15', + excerpt: 'Kako kompanije mogu koristiti AI agente za automatizaciju procesa, smanjenje troškova i sticanje konkurentskih prednosti.', + category: 'AI Razvoj', + tags: ['AI Agenti', 'Automatizacija', 'GPT-4', 'Claude', 'LLM', 'Preduzeća'], + coverImage: '/images/posts/ki-agenten-unternehmen-guide/cover.jpg', + }, + { + slug: 'n8n-make-zapier-vergleich', + title: 'n8n vs Make vs Zapier: Koji Alat za Automatizaciju je Pravi za Vas?', + date: '2026-01-10', + excerpt: 'Detaljna uporedna analiza vodećih alata za automatizaciju radnih tokova sa prednostima i manama za različite slučajeve upotrebe.', + category: 'Automatizacija', + tags: ['n8n', 'Make', 'Zapier', 'Workflow', 'Automatizacija', 'No-Code'], + coverImage: '/images/posts/n8n-make-zapier-vergleich/cover.jpg', + }, + { + slug: 'voice-ai-kundenservice-trends', + title: 'Voice AI u Korisničkoj Službi: Trendovi i Najbolje Prakse 2026', + date: '2026-01-05', + excerpt: 'Kako Voice AI revolucioniše korisničku službu i koje tehnologije treba da evaluirate za vaš posao.', + category: 'Voice AI', + tags: ['Voice AI', 'Glasovni Asistent', 'Korisnička Služba', 'NLP', 'Chatbot', 'Vapi'], + coverImage: '/images/posts/voice-ai-kundenservice-trends/cover.jpg', + }, + { + slug: 'saas-mvp-entwicklung-4-wochen', + title: 'SaaS MVP za 4 Nedelje: Od Ideje do Lansiranja', + date: '2025-12-20', + excerpt: 'Korak-po-korak vodič za brz razvoj SaaS MVP-a sa Next.js, Prisma i modernim cloud servisima.', + category: 'SaaS Razvoj', + tags: ['SaaS', 'MVP', 'Next.js', 'Startup', 'Razvoj Proizvoda', 'React'], + coverImage: '/images/posts/saas-mvp-entwicklung-4-wochen/cover.jpg', + }, { slug: 'erp-integration-breuninger', title: 'ApparelMagic i TradeByte: Analiza kompleksnih integracija', @@ -54,29 +239,131 @@ const blogPostsData: Record = { ], }; +const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com'; + +export async function generateMetadata({ params }: Props): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'blog.meta' }); + + const keywords: Record = { + de: [ + 'KI Blog', + 'Automatisierung Blog', + 'Tech Blog', + 'KI-Agenten', + 'n8n Tutorials', + 'Voice AI', + 'SaaS Entwicklung', + 'Python Tutorials', + ], + en: [ + 'AI Blog', + 'Automation Blog', + 'Tech Blog', + 'AI Agents Guide', + 'n8n Tutorials', + 'Voice AI Guide', + 'SaaS Development', + 'Python Tutorials', + 'GPT-4 Integration Guide', + 'AI Developer Blog', + 'Process Automation Tips', + ], + sr: [ + 'AI Blog', + 'Automatizacija Blog', + 'Tech Blog', + 'AI Agenti', + 'n8n Tutoriali', + 'Voice AI', + 'SaaS Razvoj', + 'Python Tutoriali', + ], + }; + + const localePath = locale === 'de' ? '/blog' : `/${locale}/blog`; + + return { + title: t('title'), + description: t('description'), + keywords: keywords[locale] || keywords.de, + alternates: { + canonical: `${BASE_URL}${localePath}`, + languages: { + 'x-default': `${BASE_URL}/blog`, + de: `${BASE_URL}/blog`, + en: `${BASE_URL}/en/blog`, + sr: `${BASE_URL}/sr/blog`, + }, + }, + openGraph: { + title: t('title'), + description: t('description'), + url: `${BASE_URL}${localePath}`, + type: 'website', + locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US', + alternateLocale: ['de_DE', 'en_US', 'sr_RS'].filter(l => l !== (locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US')), + }, + }; +} + +function getFormattedDate(date: string, locale: string) { + const languageMap: Record = { + de: 'de-DE', + en: 'en-US', + sr: 'sr-RS', + }; + + return new Date(date).toLocaleDateString(languageMap[locale] || 'de-DE', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); +} + +function getImagePath(coverImage: string) { + if (coverImage.startsWith('http')) { + return coverImage; + } + return coverImage.replace('/blog/', '/images/posts/'); +} + +// Placeholder image for posts without cover images +const PLACEHOLDER_IMAGE = 'data:image/svg+xml,' + encodeURIComponent(` + + + + + + + +`); + +// Known existing images (from public/images/posts/) +const EXISTING_IMAGES = new Set([ + '/images/posts/erp-integration-breuninger/cover.jpg', + '/images/posts/fullstack-development-timetracking/cover.jpg', + '/images/posts/rfid-automation/cover.jpg', + '/images/posts/automated-ad-creatives/cover.jpg', +]); + +// Blog image component with fallback +function BlogImage({ src, alt, fallback }: { src: string; alt: string; fallback: string }) { + const imageSrc = EXISTING_IMAGES.has(src) ? src : fallback; + + return ( + {alt} + ); +} + function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) { - - const getFormattedDate = (date: string) => { - const languageMap: Record = { - de: 'de-DE', - en: 'en-US', - sr: 'sr-RS', - }; - - return new Date(date).toLocaleDateString(languageMap[locale] || 'de-DE', { - year: 'numeric', - month: 'long', - day: 'numeric', - }); - }; - - const getImagePath = (coverImage: string) => { - if (coverImage.startsWith('http')) { - return coverImage; - } - return coverImage.replace('/blog/', '/images/posts/'); - }; - return ( - +
    {/* Image Container */}
    - {post.title}
    @@ -110,7 +390,7 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
    - {getFormattedDate(post.date)} + {getFormattedDate(post.date, locale)}
    {post.tags && post.tags.length > 0 && (
    @@ -159,110 +439,216 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
    - +
    ); } -export default function BlogPage() { - const t = useTranslations('blog'); - const locale = useLocale(); - const [posts, setPosts] = useState([]); - const [loading, setLoading] = useState(true); +// Pagination component +function Pagination({ + currentPage, + totalPages, + locale, + t, +}: { + currentPage: number; + totalPages: number; + locale: string; + t: (key: string) => string; +}) { + const getPageNumbers = () => { + const pages: (number | 'ellipsis')[] = []; - useEffect(() => { - // Simulate loading - const loadPosts = async () => { - setLoading(true); - // Get posts for current locale - const currentPosts = blogPostsData[locale] || blogPostsData.de; - setPosts(currentPosts); - setLoading(false); - }; - loadPosts(); - }, [locale]); + if (totalPages <= 7) { + // Show all pages if 7 or fewer + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + } else { + // Always show first page + pages.push(1); - if (loading) { - return ( -
    - 3) { + pages.push('ellipsis'); + } + + // Show pages around current + const start = Math.max(2, currentPage - 1); + const end = Math.min(totalPages - 1, currentPage + 1); + + for (let i = start; i <= end; i++) { + pages.push(i); + } + + if (currentPage < totalPages - 2) { + pages.push('ellipsis'); + } + + // Always show last page + pages.push(totalPages); + } + + return pages; + }; + + if (totalPages <= 1) return null; + + return ( + + ); +} + +export default async function BlogPage({ params, searchParams }: Props) { + const { locale } = await params; + const { page } = await searchParams; + setRequestLocale(locale); + + const t = await getTranslations('blog'); + + // Get dynamic posts from markdown files + const dynamicPosts = getAllBlogPosts(); + + // Get legacy posts (with translations) + const legacyPosts = legacyBlogPosts[locale] || legacyBlogPosts.de; + + // Merge: dynamic posts first, then legacy posts (avoiding duplicates) + const legacySlugs = new Set(legacyPosts.map(p => p.slug)); + const allPosts = [ + ...dynamicPosts.filter(p => !legacySlugs.has(p.slug)), + ...legacyPosts, + ].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + + // Pagination + const currentPage = Math.max(1, parseInt(page || '1', 10) || 1); + const totalPages = Math.ceil(allPosts.length / POSTS_PER_PAGE); + const validPage = Math.min(currentPage, totalPages || 1); + + const startIndex = (validPage - 1) * POSTS_PER_PAGE; + const endIndex = startIndex + POSTS_PER_PAGE; + const posts = allPosts.slice(startIndex, endIndex); return (
    {/* Header Section */} - - +
    +

    BLOG

    - - +
    +

    {t('meta.header.title')} - - +

    +

    {t('meta.header.subtitle')} - - +

    + {/* Post count */} +

    + {t('ui.pagination.showing', { + start: startIndex + 1, + end: Math.min(endIndex, allPosts.length), + total: allPosts.length, + })} +

    +
    {/* Blog Posts Grid */} - - {posts.map((post, index) => ( - - - +
    + {posts.map((post) => ( + ))} - +
    + + {/* Pagination */} + t(key)} + /> {/* Empty State */} - {posts.length === 0 && !loading && ( - + {posts.length === 0 && ( +

    {t('ui.errors.posts')}

    - +
    )}
    diff --git a/src/app/[locale]/contact/page.tsx b/src/app/[locale]/contact/page.tsx index e088ff5..2bded31 100644 --- a/src/app/[locale]/contact/page.tsx +++ b/src/app/[locale]/contact/page.tsx @@ -1,169 +1,86 @@ -'use client'; +import { setRequestLocale } from 'next-intl/server'; +import type { Metadata } from 'next'; +import { getTranslations } from 'next-intl/server'; +import { ContactForm } from '@/components/contact'; +import { BreadcrumbJsonLd, WebPageJsonLd } from '@/components/seo'; +import { type Locale } from '@/i18n/config'; -import { useTranslations } from 'next-intl'; -import { useState } from 'react'; -import { Mail, Phone, MapPin } from 'lucide-react'; -import Link from 'next/link'; -import { useParams } from 'next/navigation'; +type Props = { + params: Promise<{ locale: string }>; +}; -export default function ContactPage() { - const t = useTranslations('pages.contact'); - const params = useParams(); - const locale = params.locale as string; +const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com'; - const [formData, setFormData] = useState({ - name: '', - email: '', - message: '', - }); - const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); +export async function generateMetadata({ params }: Props): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'pages.contact' }); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setStatus('loading'); - - // Simulate form submission - try { - await new Promise((resolve) => setTimeout(resolve, 1000)); - setStatus('success'); - setFormData({ name: '', email: '', message: '' }); - } catch { - setStatus('error'); - } + const titles: Record = { + de: 'Kontakt - KI Entwickler Köln | Damjan Savić', + en: 'Contact - Remote AI Developer | Hire for USA, UK & Europe Projects', + sr: 'Kontakt - AI Developer Keln | Damjan Savić', }; + const descriptions: Record = { + de: 'Kontaktieren Sie Damjan Savić - Ihren AI & Automation Specialist aus Köln. Entwicklung von KI-Agenten, Voice AI, Prozessautomatisierung und SaaS-Lösungen.', + en: 'Hire Damjan Savić - Remote AI Developer based in Germany. Available for clients in USA, UK & Europe. Specializing in AI agents, Voice AI, n8n automation and custom SaaS solutions. Free consultation.', + sr: 'Kontaktirajte Damjana Savića - AI Developer i Automation Specialist iz Nemačke, srpskog porekla. Razvoj AI agenata, Voice AI, n8n automatizacija i SaaS rešenja za klijente iz Srbije i dijaspore.', + }; + + const keywords: Record = { + de: ['Kontakt Damjan Savić', 'KI Entwickler kontaktieren', 'Freelancer Köln', 'AI Beratung'], + en: ['Hire AI Developer', 'Contact Remote Developer', 'AI Consultant USA', 'AI Developer UK', 'Freelance AI Developer', 'n8n Expert for Hire'], + sr: ['Kontakt AI Developer', 'AI Programer Srbija', 'Freelance Programer Beograd', 'AI Konsultant Srbija', 'Softverski Razvoj Srbija', 'Remote Developer Dijaspora', 'n8n Automatizacija Srbija'], + }; + + const localePath = locale === 'de' ? '/contact' : `/${locale}/contact`; + + return { + title: titles[locale] || titles.de, + description: descriptions[locale] || descriptions.de, + keywords: keywords[locale] || keywords.de, + alternates: { + canonical: `${BASE_URL}${localePath}`, + languages: { + 'x-default': `${BASE_URL}/de/contact`, + de: `${BASE_URL}/de/contact`, + en: `${BASE_URL}/en/contact`, + sr: `${BASE_URL}/sr/contact`, + }, + }, + openGraph: { + title: titles[locale] || titles.de, + description: descriptions[locale] || descriptions.de, + url: `${BASE_URL}${localePath}`, + type: 'website', + locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US', + alternateLocale: ['de_DE', 'en_US', 'sr_RS'].filter(l => l !== (locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US')), + }, + }; +} + +export default async function ContactPage({ params }: Props) { + const { locale } = await params; + setRequestLocale(locale); + + const t = await getTranslations({ locale, namespace: 'pages.contact' }); + const commonT = await getTranslations({ locale, namespace: 'common.nav' }); + + const breadcrumbItems = [ + { name: commonT('home'), url: `/${locale}` }, + { name: commonT('contact'), url: `/${locale}/contact` }, + ]; + return ( -
    -
    - {/* Breadcrumb */} - - - {/* Hero */} -
    -

    {t('hero.title')}

    -

    - {t('hero.subtitle')} -

    -
    - -
    - {/* Contact Info */} -
    -

    {t('contactInfo.title')}

    -

    {t('contactInfo.description')}

    - -
    -
    -
    - -
    -
    -

    {t('contactInfo.address.label')}

    -

    {t('contactInfo.address.value')}

    -
    -
    - -
    -
    - -
    -
    -

    {t('contactInfo.phone.label')}

    - - {t('contactInfo.phone.value')} - -
    -
    - -
    -
    - -
    -
    -

    {t('contactInfo.email.label')}

    - - {t('contactInfo.email.value')} - -
    -
    -
    -
    - - {/* Contact Form */} -
    -

    {t('contactForm.title')}

    -

    {t('contactForm.description')}

    - -
    -
    - - setFormData({ ...formData, name: e.target.value })} - placeholder={t('contactForm.name.placeholder')} - required - className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
    - -
    - - setFormData({ ...formData, email: e.target.value })} - placeholder={t('contactForm.email.placeholder')} - required - className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
    - -
    - -