
Headless CMS in Ungarn: Sanity vs. Strapi vs. Contentful 2026
Drei Headless-CMS im Vergleich: Sanity, Strapi, Contentful. Pricing, Echtzeit-Vorschau, Lokalisierung und 5 Anwendungsfälle für den ungarischen Markt.
25 konkrete Prüfpunkte, die ein Next.js-16-Projekt vor dem Go-live erfüllen muss – Security, Performance, SEO, Barrierefreiheit und Monitoring in einer Liste.

Checkliste vor dem Launch
CSP, HSTS, X-Frame, Referrer-Policy. Rate-Limiting für kritische Endpunkte, .env niemals in Git committen.
SSG oder ISR wo immer möglich, next/image mit sizes-Prop, Bundle-Analyzer in der CI. Lighthouse 90+ auf Mobilgeräten.
Metadata, JSON-LD, Sitemap und robots.txt. hreflang für Multi-Locale-Routen. WCAG-AA-Kontrast und vollständige Tastaturnavigation.
Sentry mit Source-Map-Upload, Vercel Analytics oder Plausible, plus Uptime-Monitoring mit Slack-Anbindung.
Next.js 16 ist Anfang 2026 in der stabilen Version verfügbar, mit Turbopack als Standard und neuen App-Router-Features. Für ein production-ready Deployment gibt es jedoch viele kleine Punkte, die leicht vergessen werden. Diese Checkliste umfasst 25 konkrete Punkte, kategorisiert. Die Punkte spiegeln den Stand von Next.js 16.2+ aus dem ersten Quartal 2026 wider; neue Releases können Änderungen mit sich bringen, es lohnt sich, auch die offizielle Dokumentation zu konsultieren.
Die 25 Punkte sind nicht vollständig – auf Projektebene können weitere hinzukommen (DSGVO, Analytics-Segmentierung, A/B-Test-Infrastruktur). Aber mit dieser Checkliste gibt es am Launch-Tag keine Panik nach dem Motto „das hätten wir auch noch machen sollen".
Bevor wir mit der Checkliste beginnen, hier einige Next.js-16-Änderungen, die das Production-Deployment beeinflussen:
cookies(), headers(), params und searchParams sind jetzt alle asynchron. Beim Upgrade einer bestehenden Codebasis ist eine Anpassung zwingend erforderlich.use()-Hook, useFormStatus, useActionState.'use cache'-Direktive für granulares Caching.Achtung: Die Breaking Changes werden beim Upgrade größtenteils automatisch vom Befehl
npx @next/codemod upgradebehandelt. Eine manuelle Prüfung ist aber trotzdem nötig — insbesondere bei benutzerdefinierten Middlewares und dem useSearchParams-Hook.
CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. Eine minimale Konfiguration:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
].join('; '),
},
],
},
];
},
};
export default nextConfig;
CSP-Nonce-Lösung für dynamische Inline-Scripts: Verwendung von next/script oder ein im Middleware generierter Nonce.
In .gitignore durch das Muster .env* abgedeckt. Nur .env.example liegt als Vorlage im Repository.
# .gitignore
.env
.env.local
.env.*.local
.env.production
# .env.example
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://user:pass@host:5432/db
OPENAI_API_KEY=sk-...
Als zweite Schutzebene gegen Secret-Leaks: git-secrets oder gitleaks in der CI.
Next.js 15+ verfügt über eine eingebaute Origin-Header-Prüfung. Schalten Sie diese nicht manuell aus. Die Server Action validiert automatisch, dass die Anfrage von der eigenen Domain stammt.
// app/actions/contact.ts
'use server';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
message: z.string().min(10).max(2000),
});
export async function submitContact(formData: FormData) {
const parsed = schema.safeParse({
email: formData.get('email'),
message: formData.get('message'),
});
if (!parsed.success) {
return { error: 'Invalid input', issues: parsed.error.issues };
}
// Server-seitige Action — standardmäßig CSRF-geschützt
await db.contacts.insert(parsed.data);
return { success: true };
}
Vercel KV / Upstash Redis + @upstash/ratelimit. Formularversand, Login, API-Aufrufe.
// middleware.ts
import { NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '60 s'),
analytics: true,
});
export async function middleware(request) {
if (request.nextUrl.pathname.startsWith('/api/')) {
const ip = request.ip ?? '127.0.0.1';
const { success, limit, remaining } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse('Too Many Requests', {
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
},
});
}
}
return NextResponse.next();
}
</-Zeichen escapenIn das JSON-LD-Script injiziert kann das ein XSS-Risiko darstellen:
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(schema).replace(/</g, '\\u003c'),
}}
/>
Das Zeichen </ muss escaped werden, da z. B. ein </script>-Substring aus dem Script-Tag ausbrechen kann.
npm audit sauber (0 Schwachstellen). Wenn eine Next.js-Abhängigkeit das Fixen verhindert, mit npm overrides erzwingen.
{
"overrides": {
"postcss": "^8.4.31"
}
}
Zusätzlich in der CI:
# .github/workflows/security.yml
- name: Dependency audit
run: npm audit --audit-level=high
- name: License check
run: npx license-checker --failOn 'GPL-3.0'
generateStaticParams für jede dynamische Route. ISR (revalidate), wenn sich der Inhalt ändert, aber nicht in Echtzeit. RSC ist Standard, Client Components nur dort, wo sie wirklich nötig sind.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export const revalidate = 3600; // 1 Stunde ISR
export default async function BlogPost({ params }) {
const { slug } = await params;
const post = await getPostBySlug(slug);
return <Article post={post} />;
}
Für jedes Rasterbild. placeholder="blur" und die sizes-Prop angeben.
import Image from 'next/image';
<Image
src="/blog/images/post-slug/hero.jpg"
alt="Descriptive alt text"
width={1200}
height={630}
sizes="(max-width: 768px) 100vw, 1200px"
priority={isAboveFold}
placeholder="blur"
blurDataURL="data:image/svg+xml;base64,..."
/>
Die priority-Prop für Above-the-Fold-Bilder — sie erzwingt Eager Loading.
import { Inter, Geist_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin', 'latin-ext'], // ungarische Zeichen
display: 'swap',
variable: '--font-sans',
});
const geistMono = Geist_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-mono',
});
Mit subsets werden nur die benötigten Zeichen geladen. Für die ungarische Sprache ist latin-ext zwingend erforderlich (ű, ő usw.). Die Self-Hosted-Option ist an die Build-Zeit gekoppelt.
@next/bundle-analyzer-Plugin. Regressionsprüfung in der CI: Wenn das Bundle um 20 % wächst, wird ein Alert ausgelöst.
// next.config.ts
import bundleAnalyzer from '@next/bundle-analyzer';
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
export default withBundleAnalyzer({...});
ANALYZE=true npm run build
# Öffnet den Bundle-Tree im Browser
Beispiel für die CI-Integration mit size-limit:
{
"size-limit": [
{
"path": ".next/static/chunks/*.js",
"limit": "180 KB"
}
]
}
Der App Router splittet standardmäßig. Vermeiden Sie die 'use client'-Direktive auf Root-Ebene — setzen Sie sie gezielt nur bei einzelnen Komponenten ein.
// page.tsx — Server Component (Standard)
import ClientCounter from './client-counter';
export default async function Page() {
const data = await fetchServerData();
return (
<div>
<h1>{data.title}</h1>
{/* Nur diese Komponente wird client-seitig gerendert */}
<ClientCounter initial={data.count} />
</div>
);
}
// client-counter.tsx — Client Component
'use client';
import { useState } from 'react';
export default function ClientCounter({ initial }) {
const [count, setCount] = useState(initial);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
loading="lazy" ist bei next/image Standard. Bei Above-the-Fold-Bildern priority explizit setzen. Alle anderen Bilder werden erst geladen, wenn sie in den Viewport gelangen.
Cache-Control: public, max-age=31536000, immutable)cache: 'force-cache' oder 'no-store'// Server Component
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }, // 1 Stunde ISR
});
return res.json();
}
In Next.js 16 bietet die 'use cache'-Direktive noch granularere Kontrolle:
async function getProductList() {
'use cache';
cacheLife('hours');
cacheTag('products');
return db.products.findAll();
}
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Page title — Brand',
description: 'Page description ~150 char',
openGraph: {
title: 'OG title',
description: 'OG description',
images: ['/og-image.jpg'],
locale: 'hu_HU',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'Twitter title',
description: 'Twitter description',
images: ['/twitter-image.jpg'],
},
alternates: {
canonical: 'https://example.com/page',
languages: {
'hu-HU': 'https://example.com/hu/page',
'en-US': 'https://example.com/en/page',
},
},
};
Dynamische Metadata:
export async function generateMetadata({ params }): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: { images: [post.image] },
};
}
src/app/sitemap.ts — native Next.js-Unterstützung. Enthält alle statischen und dynamischen Routen.
// app/sitemap.ts
import type { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://example.com';
const posts = await getAllPosts();
const blogUrls = posts.map((p) => ({
url: `${baseUrl}/blog/${p.slug}`,
lastModified: p.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.7,
}));
return [
{ url: baseUrl, priority: 1.0 },
{ url: `${baseUrl}/blog`, priority: 0.8 },
...blogUrls,
];
}
In Form von src/app/robots.ts. In der Dev-/Staging-Umgebung Disallow: / für alle User-Agents.
// app/robots.ts
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const isProduction = process.env.NEXT_PUBLIC_ENV === 'production';
if (!isProduction) {
return {
rules: { userAgent: '*', disallow: '/' },
};
}
return {
rules: [
{ userAgent: '*', allow: '/', disallow: ['/api/', '/admin/'] },
],
sitemap: 'https://example.com/sitemap.xml',
};
}
Organization, BlogPosting, FAQPage, Product-Schema je nach Relevanz. Validierung mit dem Schema Markup Validator.
const schema = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
datePublished: post.date,
author: {
'@type': 'Organization',
name: 'Corevanix',
},
image: post.coverImage,
};
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(schema).replace(/</g, '\\u003c'),
}}
/>
alternates.languages in den Metadata auf jeder Multi-Locale-Seite. Getestet mit der Google Search Console.
Jedes interaktive Element ist per Tastatur erreichbar. Ein sichtbarer Focus-Ring (focus-visible:ring-2).
<button
className="rounded bg-accent px-4 py-2 focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-accent-primary
focus-visible:ring-offset-2"
>
Click me
</button>
aria-label auf jedem Icon-Button, aria-labelledby in Abschnitten, aria-expanded bei Accordions.
<button aria-label="Bezárás" onClick={onClose}>
<X aria-hidden="true" />
</button>
<nav aria-label="Főnavigáció">
<ul>...</ul>
</nav>
<button aria-expanded={isOpen} aria-controls="menu">
Menu
</button>
Text zu Hintergrund mind. 4.5:1, große Schrift 3:1. Der Lighthouse-Audit prüft dies, zusätzlich eine manuelle Kontrolle mit Tools wie dem WebAIM Contrast Checker.
Bei Tailwind Dark/Light Mode müssen beide Varianten geprüft werden.
<a
href="#main"
className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4
focus:bg-accent-primary focus:px-4 focus:py-2 focus:rounded"
>
Skip to main content
</a>
<main id="main">...</main>
Einrichtung von @sentry/nextjs. Source-Map-Upload beim Vercel-Build. Release-Tracking mit Commit-SHA.
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
environment: process.env.NEXT_PUBLIC_ENV,
release: process.env.NEXT_PUBLIC_COMMIT_SHA,
});
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.05,
replaysOnErrorSampleRate: 1.0,
integrations: [Sentry.replayIntegration()],
});
Vercel Analytics (Free Tier), Plausible oder PostHog. DSGVO-konform und bevorzugt cookielos.
import { Analytics } from '@vercel/analytics/next';
import { SpeedInsights } from '@vercel/speed-insights/next';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}
UptimeRobot (kostenlos), Better Stack oder das in Vercel integrierte Monitoring. Slack-/E-Mail-Alert bei Downtime.
Empfohlenes Setup:
Bevor Sie in den main-Branch pushen:
npm run lint # 0 Fehler
npm run typecheck # 0 Fehler
npm run build # erfolgreich, keine Warnung
npm run start # Smoke-Test auf localhost
npx unlighthouse # Lighthouse 90+ in allen Kategorien
| Kategorie | Ziel | Ideal |
|---|---|---|
| Performance | 90+ | 95+ |
| Barrierefreiheit | 95+ | 100 |
| Best Practices | 95+ | 100 |
| SEO | 95+ | 100 |
Gemessen auf einem Mobilgerät (Mid-Range). Auf dem Laptop erreicht fast jeder 100 Punkte — mobil ist release-kritisch.
| Metrik | Gut | Verbesserungsbedürftig | Schlecht |
|---|---|---|---|
| LCP | < 2.5s | 2.5-4.0s | > 4.0s |
| CLS | < 0.1 | 0.1-0.25 | > 0.25 |
| INP | < 200ms | 200-500ms | > 500ms |
// app/error.tsx
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
export default function Error({ error, reset }) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<div>
<h2>Valami hiba történt</h2>
<button onClick={() => reset()}>Újrapróbálás</button>
</div>
);
}
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
<div>
<h2>Az oldal nem található</h2>
<Link href="/">Vissza a főoldalra</Link>
</div>
);
}
// app/blog/[slug]/loading.tsx
export default function Loading() {
return <BlogPostSkeleton />;
}
// app/layout.tsx
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
<link rel="dns-prefetch" href="https://www.googletagmanager.com" />
</head>
{
"framework": "nextjs",
"buildCommand": "npm run build",
"regions": ["fra1"],
"github": {
"silent": false
}
}
Die Region fra1 (Frankfurt) für EU-Daten. Multi-Region nur bei großem globalem Traffic.
Verwandte Artikel von uns: Headless CMS in Ungarn — Content-Management neben Next.js. Conversion-Rate-Optimierung im Webshop — E-Commerce-Performance. 7 Schutzmaßnahmen gegen LLM-Halluzinationen — wenn Sie ein AI-Feature in Next.js bauen.
Die 25 Punkte sind nicht vollständig — auf Projektebene können weitere hinzukommen. Aber mit dieser Checkliste gibt es am Launch-Tag keine Panik nach dem Motto „das hätten wir auch noch machen sollen".
Das Pre-Launch-Audit ist ein 1-2-tägiger Prozess: die 25+5 Punkte durchgehen, messen, die Lücken schließen. Das gehört nicht in die erste Hyper-Care-Woche, sondern in den letzten Sprint vor dem Launch.
Wenn Sie ein Next.js-Projekt für Production planen, lassen Sie uns über die Deployment-Strategie sprechen — für jeden der 25 Punkte haben wir eine bewährte Lösung. Beim ersten Release begleitet Sie unser Partner-Entwickler persönlich durch die gesamte Checkliste, damit die Übergabe reibungslos verläuft.
Über den Autor
Corevanix Kft.
Technologiepartner
Technologiepartner aus Budapest — SAP/ERP-Integration, Webentwicklung, KI-Automatisierung und Mobile-App-Entwicklung. Wir arbeiten in der eigenen Umgebung des Kunden, und der ausgelieferte Code gehört vollständig dem Kunden.

Drei Headless-CMS im Vergleich: Sanity, Strapi, Contentful. Pricing, Echtzeit-Vorschau, Lokalisierung und 5 Anwendungsfälle für den ungarischen Markt.

Zehn technische Optimierungen, die die Conversion-Rate im Onlineshop messbar steigern: Performance, UX, Trust-Signale, Checkout-Flow und A/B-Tests.