diff --git a/app/api/pages/[slug]/pdf/route.tsx b/app/api/pages/[slug]/pdf/route.tsx
new file mode 100644
index 00000000..e6380351
--- /dev/null
+++ b/app/api/pages/[slug]/pdf/route.tsx
@@ -0,0 +1,64 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getPayload } from 'payload';
+import configPromise from '@payload-config';
+import { renderToStream } from '@react-pdf/renderer';
+import React from 'react';
+import { PDFPage } from '@/lib/pdf-page';
+
+export async function GET(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
+ try {
+ const { slug } = await params;
+
+ // Get Payload App
+ const payload = await getPayload({ config: configPromise });
+
+ // Fetch the page
+ const pages = await payload.find({
+ collection: 'pages',
+ where: {
+ slug: { equals: slug },
+ _status: { equals: 'published' },
+ },
+ limit: 1,
+ });
+
+ if (pages.totalDocs === 0) {
+ return new NextResponse('Page not found', { status: 404 });
+ }
+
+ const page = pages.docs[0];
+
+ // Determine locale from searchParams or default to 'de'
+ const searchParams = req.nextUrl.searchParams;
+ const locale = (searchParams.get('locale') as 'en' | 'de') || 'de';
+
+ // Render the React-PDF document into a stream
+ const stream = await renderToStream();
+
+ // Pipe the Node.js Readable stream into a valid fetch/Web Response stream
+ const body = new ReadableStream({
+ start(controller) {
+ stream.on('data', (chunk) => controller.enqueue(chunk));
+ stream.on('end', () => controller.close());
+ stream.on('error', (err) => controller.error(err));
+ },
+ cancel() {
+ (stream as any).destroy?.();
+ },
+ });
+
+ const filename = `${slug}.pdf`;
+
+ return new NextResponse(body, {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/pdf',
+ 'Content-Disposition': `attachment; filename="${filename}"`,
+ // Cache control if needed, skip for now.
+ },
+ });
+ } catch (error) {
+ console.error('Error generating PDF:', error);
+ return new NextResponse('Internal Server Error', { status: 500 });
+ }
+}
diff --git a/lib/pdf-page.tsx b/lib/pdf-page.tsx
new file mode 100644
index 00000000..b46627fd
--- /dev/null
+++ b/lib/pdf-page.tsx
@@ -0,0 +1,289 @@
+import * as React from 'react';
+import { Document, Page, View, Text, Image, StyleSheet, Font, Link } from '@react-pdf/renderer';
+
+// Register fonts (using system fonts for now, can be customized)
+Font.register({
+ family: 'Helvetica',
+ fonts: [
+ { src: '/fonts/Helvetica.ttf', fontWeight: 400 },
+ { src: '/fonts/Helvetica-Bold.ttf', fontWeight: 700 },
+ ],
+});
+
+// ─── Brand Tokens ────────────────────────────────────────
+
+const C = {
+ navy: '#001a4d',
+ navyDeep: '#000d26',
+ green: '#4da612',
+ white: '#FFFFFF',
+ offWhite: '#f8f9fa',
+ gray200: '#e5e7eb',
+ gray400: '#9ca3af',
+ gray600: '#4b5563',
+ gray900: '#111827',
+};
+
+const MARGIN = 56;
+const HEADER_H = 52;
+const FOOTER_H = 48;
+const BODY_TOP = HEADER_H + 20;
+
+const styles = StyleSheet.create({
+ page: {
+ color: C.gray900,
+ lineHeight: 1.5,
+ backgroundColor: C.white,
+ paddingTop: BODY_TOP,
+ paddingBottom: FOOTER_H + 40,
+ paddingHorizontal: MARGIN,
+ fontFamily: 'Helvetica',
+ },
+ header: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: HEADER_H,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'flex-end',
+ paddingHorizontal: MARGIN,
+ paddingBottom: 12,
+ borderBottomWidth: 0,
+ },
+ logoText: {
+ fontSize: 16,
+ fontWeight: 700,
+ color: C.navy,
+ },
+ docTitleLabel: {
+ fontSize: 7,
+ fontWeight: 700,
+ color: C.gray400,
+ letterSpacing: 1.2,
+ textTransform: 'uppercase',
+ },
+ footer: {
+ position: 'absolute',
+ bottom: 20,
+ left: MARGIN,
+ right: MARGIN,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ borderTopWidth: 0.5,
+ borderTopColor: C.gray200,
+ paddingTop: 8,
+ },
+ footerText: {
+ fontSize: 7,
+ color: C.gray400,
+ letterSpacing: 0.8,
+ textTransform: 'uppercase',
+ },
+ pageTitle: {
+ fontSize: 24,
+ fontWeight: 700,
+ color: C.navyDeep,
+ marginBottom: 16,
+ textTransform: 'uppercase',
+ letterSpacing: -0.5,
+ },
+ accentBar: {
+ width: 40,
+ height: 3,
+ backgroundColor: C.green,
+ marginBottom: 24,
+ },
+
+ // Lexical Elements
+ paragraph: {
+ fontSize: 10,
+ color: C.gray600,
+ lineHeight: 1.6,
+ marginBottom: 12,
+ },
+ heading1: {
+ fontSize: 18,
+ fontWeight: 700,
+ color: C.navyDeep,
+ marginTop: 16,
+ marginBottom: 8,
+ },
+ heading2: {
+ fontSize: 14,
+ fontWeight: 700,
+ color: C.navyDeep,
+ marginTop: 14,
+ marginBottom: 6,
+ },
+ heading3: {
+ fontSize: 12,
+ fontWeight: 700,
+ color: C.navyDeep,
+ marginTop: 12,
+ marginBottom: 4,
+ },
+ list: {
+ marginBottom: 12,
+ marginLeft: 8,
+ },
+ listItem: {
+ flexDirection: 'row',
+ marginBottom: 4,
+ },
+ listItemBullet: {
+ width: 12,
+ fontSize: 10,
+ color: C.green,
+ },
+ listItemContent: {
+ flex: 1,
+ fontSize: 10,
+ color: C.gray600,
+ lineHeight: 1.6,
+ },
+ link: {
+ color: C.green,
+ textDecoration: 'none',
+ },
+ textBold: {
+ fontWeight: 700,
+ fontFamily: 'Helvetica-Bold',
+ color: C.navyDeep,
+ },
+ textItalic: {
+ fontStyle: 'italic', // Not actually working without proper font set, but we will fallback
+ },
+});
+
+// ─── Lexical to React-PDF Renderer ────────────────────────────────
+
+const renderLexicalNode = (node: any, idx: number): React.ReactNode => {
+ if (!node) return null;
+
+ switch (node.type) {
+ case 'text': {
+ const format = node.format || 0;
+ const isBold = (format & 1) !== 0;
+ const isItalic = (format & 2) !== 0;
+
+ let elementStyle: any = {};
+ if (isBold) elementStyle = { ...elementStyle, ...styles.textBold };
+ if (isItalic) elementStyle = { ...elementStyle, ...styles.textItalic };
+
+ return (
+
+ {node.text}
+
+ );
+ }
+
+ case 'paragraph': {
+ return (
+
+ {node.children?.map((child: any, i: number) => renderLexicalNode(child, i))}
+
+ );
+ }
+
+ case 'heading': {
+ let hStyle = styles.heading3;
+ if (node.tag === 'h1') hStyle = styles.heading1;
+ if (node.tag === 'h2') hStyle = styles.heading2;
+
+ return (
+
+ {node.children?.map((child: any, i: number) => renderLexicalNode(child, i))}
+
+ );
+ }
+
+ case 'list': {
+ return (
+
+ {node.children?.map((child: any, i: number) => {
+ if (child.type === 'listitem') {
+ return (
+
+
+ {node.listType === 'number' ? `${i + 1}.` : '•'}
+
+
+ {child.children?.map((c: any, ci: number) => renderLexicalNode(c, ci))}
+
+
+ );
+ }
+ return renderLexicalNode(child, i);
+ })}
+
+ );
+ }
+
+ case 'link': {
+ const href = node.fields?.url || node.url || '#';
+ return (
+
+ {node.children?.map((child: any, i: number) => renderLexicalNode(child, i))}
+
+ );
+ }
+
+ case 'linebreak': {
+ // React-PDF doesn't handle `
`, but a newline char usually works inside ``.
+ return {'\n'};
+ }
+
+ // Ignore payload blocks recursively to avoid crashing, as pages should mainly use rich text
+ case 'block':
+ return null;
+
+ default:
+ if (node.children) {
+ return (
+
+ {node.children.map((child: any, i: number) => renderLexicalNode(child, i))}
+
+ );
+ }
+ return null;
+ }
+};
+
+interface PDFPageProps {
+ page: any;
+ locale?: string;
+}
+
+export const PDFPage: React.FC = ({ page, locale = 'de' }) => {
+ const dateStr = new Date().toLocaleDateString(locale === 'en' ? 'en-US' : 'de-DE', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+
+ return (
+
+
+
+ KLZ
+ {locale === 'en' ? 'Document' : 'Dokument'}
+
+
+ {page.title}
+
+
+
+ {page.content?.root?.children?.map((node: any, i: number) => renderLexicalNode(node, i))}
+
+
+
+ KLZ CABLES
+ {dateStr}
+
+
+
+ );
+};