Initial commit: Portfolio Website

Vollständige Next.js 15 Portfolio-Website mit:
- Blog-System mit 100+ Artikeln
- Supabase-Integration
- Responsive Design mit Tailwind CSS
- TypeScript-Konfiguration
- Testing-Setup mit Vitest und Playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 15:07:20 +01:00
co-authored by Claude Opus 4.5
commit e1bbe5455d
315 changed files with 94124 additions and 0 deletions
+584
View File
@@ -0,0 +1,584 @@
# Technical SEO Checklist
**Meta-Description:** Komplette Technical SEO Checklist für 2026. Crawling, Indexierung, Site Structure und Performance-Optimierung für bessere Rankings.
**Keywords:** Technical SEO, SEO Checklist, Crawling, Indexierung, Site Structure, Page Speed, Mobile SEO, SEO Audit
---
## Einführung
**Technical SEO** bildet das Fundament für erfolgreiche Suchmaschinenoptimierung. Ohne saubere technische Basis nützt der beste Content wenig. Diese Checklist deckt alle wichtigen Aspekte ab von **Crawling** bis **Performance**.
---
## Technical SEO Overview
```
┌─────────────────────────────────────────────────────────────┐
│ TECHNICAL SEO PYRAMID │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ Content │ │
│ │ & UX │ │
│ └────┬────┘ │
│ ┌─────────┴─────────┐ │
│ │ On-Page SEO │ │
│ │ (Title, Meta,H1) │ │
│ └─────────┬─────────┘ │
│ ┌──────────────┴──────────────┐ │
│ │ Site Architecture │ │
│ │ (URLs, Internal Links) │ │
│ └──────────────┬──────────────┘ │
│ ┌───────────────────┴───────────────────┐ │
│ │ Crawling & Indexing │ │
│ │ (robots.txt, sitemap, canonicals) │ │
│ └───────────────────┬───────────────────┘ │
│ ┌──────────────────────┴──────────────────────┐ │
│ │ Server & Performance │ │
│ │ (HTTPS, Speed, Core Web Vitals, Mobile) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Priority Levels: │
│ 🔴 Critical - Must fix immediately │
│ 🟡 Important - Fix soon │
│ 🟢 Nice to have - Optimize when possible │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Server & Security
```typescript
// lib/seo/audit/server.ts
interface ServerAuditResult {
https: boolean;
httpToHttpsRedirect: boolean;
wwwRedirect: 'www' | 'non-www' | 'inconsistent';
responseTime: number;
serverHeaders: Record<string, string>;
issues: string[];
}
async function auditServer(url: string): Promise<ServerAuditResult> {
const issues: string[] = [];
// HTTPS Check
const https = url.startsWith('https://');
if (!https) {
issues.push('🔴 Site is not using HTTPS');
}
// HTTP to HTTPS Redirect
const httpUrl = url.replace('https://', 'http://');
const httpResponse = await fetch(httpUrl, { redirect: 'manual' });
const httpToHttpsRedirect = httpResponse.status === 301 &&
httpResponse.headers.get('location')?.startsWith('https://');
if (!httpToHttpsRedirect) {
issues.push('🔴 HTTP does not redirect to HTTPS');
}
// WWW Consistency
const withWww = url.includes('www.');
const testUrl = withWww
? url.replace('www.', '')
: url.replace('://', '://www.');
const wwwResponse = await fetch(testUrl, { redirect: 'manual' });
const wwwRedirect = wwwResponse.status === 301 ? (withWww ? 'www' : 'non-www') : 'inconsistent';
if (wwwRedirect === 'inconsistent') {
issues.push('🟡 WWW and non-WWW versions not properly redirected');
}
// Response Time
const start = Date.now();
await fetch(url);
const responseTime = Date.now() - start;
if (responseTime > 500) {
issues.push(`🟡 Server response time is slow: ${responseTime}ms`);
}
// Security Headers
const response = await fetch(url);
const serverHeaders: Record<string, string> = {};
response.headers.forEach((value, key) => {
serverHeaders[key] = value;
});
const requiredHeaders = [
'strict-transport-security',
'x-content-type-options',
'x-frame-options'
];
requiredHeaders.forEach(header => {
if (!serverHeaders[header]) {
issues.push(`🟢 Missing security header: ${header}`);
}
});
return {
https,
httpToHttpsRedirect,
wwwRedirect,
responseTime,
serverHeaders,
issues
};
}
```
```typescript
// middleware.ts - Security Headers
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Security Headers
response.headers.set('X-DNS-Prefetch-Control', 'on');
response.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions Policy
response.headers.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=(self), interest-cohort=()'
);
return response;
}
```
---
## Crawling & Indexing
```typescript
// app/robots.ts - Optimized robots.txt
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/api/',
'/admin/',
'/_next/',
'/private/',
'/*.json$',
'/*?*', // Query Parameter
'/search?*'
]
},
{
userAgent: 'Googlebot',
allow: '/',
disallow: ['/admin/', '/api/']
},
{
// Block bad bots
userAgent: ['AhrefsBot', 'SemrushBot', 'DotBot'],
disallow: '/'
}
],
sitemap: `${baseUrl}/sitemap.xml`,
host: baseUrl
};
}
// app/sitemap.ts - Dynamic Sitemap
import { MetadataRoute } from 'next';
import { getAllPages, getAllPosts, getAllProducts } from '@/lib/content';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
// Static Pages
const staticPages: MetadataRoute.Sitemap = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1.0
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8
},
{
url: `${baseUrl}/contact`,
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 0.5
},
{
url: `${baseUrl}/blog`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.9
}
];
// Dynamic Pages
const pages = await getAllPages();
const dynamicPages: MetadataRoute.Sitemap = pages.map(page => ({
url: `${baseUrl}/${page.slug}`,
lastModified: new Date(page.updatedAt),
changeFrequency: 'monthly',
priority: 0.7
}));
// Blog Posts
const posts = await getAllPosts();
const blogPosts: MetadataRoute.Sitemap = posts.map(post => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly',
priority: 0.8
}));
// Products
const products = await getAllProducts();
const productPages: MetadataRoute.Sitemap = products.map(product => ({
url: `${baseUrl}/products/${product.slug}`,
lastModified: new Date(product.updatedAt),
changeFrequency: 'daily',
priority: 0.9
}));
return [...staticPages, ...dynamicPages, ...blogPosts, ...productPages];
}
```
```typescript
// lib/seo/canonical.ts - Canonical URL Helper
export function getCanonicalUrl(path: string, locale?: string): string {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
// Remove trailing slash
const cleanPath = path.replace(/\/$/, '');
// Remove query parameters for canonical
const pathWithoutQuery = cleanPath.split('?')[0];
// Add locale if needed
if (locale && locale !== 'de') {
return `${baseUrl}/${locale}${pathWithoutQuery}`;
}
return `${baseUrl}${pathWithoutQuery}`;
}
// Usage in page
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
return {
alternates: {
canonical: getCanonicalUrl(`/blog/${params.slug}`)
}
};
}
```
---
## URL Structure
```typescript
// lib/seo/url-utils.ts
export function createSEOFriendlyUrl(title: string): string {
return title
.toLowerCase()
.trim()
// Umlaute ersetzen
.replace(/ä/g, 'ae')
.replace(/ö/g, 'oe')
.replace(/ü/g, 'ue')
.replace(/ß/g, 'ss')
// Sonderzeichen entfernen
.replace(/[^a-z0-9\s-]/g, '')
// Mehrere Leerzeichen zu einem
.replace(/\s+/g, ' ')
// Leerzeichen zu Bindestrich
.replace(/\s/g, '-')
// Mehrere Bindestriche zu einem
.replace(/-+/g, '-')
// Max Länge
.substring(0, 60);
}
// URL Audit
interface URLAuditResult {
url: string;
issues: string[];
suggestions: string[];
}
export function auditURL(url: string): URLAuditResult {
const issues: string[] = [];
const suggestions: string[] = [];
// Length check
if (url.length > 75) {
issues.push('🟡 URL is too long (> 75 characters)');
}
// Uppercase check
if (url !== url.toLowerCase()) {
issues.push('🟡 URL contains uppercase characters');
}
// Underscore check
if (url.includes('_')) {
issues.push('🟡 URL contains underscores (use hyphens instead)');
}
// Double slashes
if (url.includes('//') && !url.startsWith('http')) {
issues.push('🔴 URL contains double slashes');
}
// Query parameters
if (url.includes('?')) {
suggestions.push('Consider using clean URLs without query parameters');
}
// Trailing slash consistency
if (!url.endsWith('/') && !url.includes('.')) {
suggestions.push('Consider adding trailing slash for consistency');
}
return { url, issues, suggestions };
}
```
---
## On-Page SEO
```typescript
// lib/seo/audit/onpage.ts
interface OnPageAuditResult {
title: { value: string; length: number; issues: string[] };
metaDescription: { value: string; length: number; issues: string[] };
h1: { count: number; value: string; issues: string[] };
headings: { h2: number; h3: number; h4: number; issues: string[] };
images: { total: number; withoutAlt: number; issues: string[] };
links: { internal: number; external: number; broken: number; issues: string[] };
}
async function auditOnPage(html: string, url: string): Promise<OnPageAuditResult> {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Title
const title = doc.querySelector('title')?.textContent || '';
const titleIssues: string[] = [];
if (!title) titleIssues.push('🔴 Missing title tag');
else if (title.length < 30) titleIssues.push('🟡 Title too short (< 30 chars)');
else if (title.length > 60) titleIssues.push('🟡 Title too long (> 60 chars)');
// Meta Description
const metaDesc = doc.querySelector('meta[name="description"]')?.getAttribute('content') || '';
const metaDescIssues: string[] = [];
if (!metaDesc) metaDescIssues.push('🔴 Missing meta description');
else if (metaDesc.length < 120) metaDescIssues.push('🟡 Meta description too short');
else if (metaDesc.length > 160) metaDescIssues.push('🟡 Meta description too long');
// H1
const h1Elements = doc.querySelectorAll('h1');
const h1Issues: string[] = [];
if (h1Elements.length === 0) h1Issues.push('🔴 Missing H1 tag');
else if (h1Elements.length > 1) h1Issues.push('🟡 Multiple H1 tags found');
// Heading Structure
const h2Count = doc.querySelectorAll('h2').length;
const h3Count = doc.querySelectorAll('h3').length;
const h4Count = doc.querySelectorAll('h4').length;
const headingIssues: string[] = [];
if (h2Count === 0) headingIssues.push('🟡 No H2 headings found');
// Images
const images = doc.querySelectorAll('img');
const imagesWithoutAlt = Array.from(images).filter(
img => !img.getAttribute('alt')
).length;
const imageIssues: string[] = [];
if (imagesWithoutAlt > 0) {
imageIssues.push(`🟡 ${imagesWithoutAlt} images missing alt text`);
}
// Links
const links = doc.querySelectorAll('a[href]');
const baseUrl = new URL(url);
let internalLinks = 0;
let externalLinks = 0;
const linkIssues: string[] = [];
links.forEach(link => {
const href = link.getAttribute('href') || '';
if (href.startsWith('/') || href.includes(baseUrl.hostname)) {
internalLinks++;
} else if (href.startsWith('http')) {
externalLinks++;
// Check for nofollow on external links
const rel = link.getAttribute('rel') || '';
if (!rel.includes('nofollow') && !rel.includes('sponsored')) {
// Suggestion, not issue
}
}
});
return {
title: { value: title, length: title.length, issues: titleIssues },
metaDescription: { value: metaDesc, length: metaDesc.length, issues: metaDescIssues },
h1: { count: h1Elements.length, value: h1Elements[0]?.textContent || '', issues: h1Issues },
headings: { h2: h2Count, h3: h3Count, h4: h4Count, issues: headingIssues },
images: { total: images.length, withoutAlt: imagesWithoutAlt, issues: imageIssues },
links: { internal: internalLinks, external: externalLinks, broken: 0, issues: linkIssues }
};
}
```
---
## Complete SEO Checklist
```typescript
// lib/seo/checklist.ts
export const technicalSEOChecklist = {
server: [
{ id: 'https', priority: 'critical', item: 'HTTPS enabled', automated: true },
{ id: 'http-redirect', priority: 'critical', item: 'HTTP redirects to HTTPS (301)', automated: true },
{ id: 'www-redirect', priority: 'important', item: 'WWW/non-WWW consistency', automated: true },
{ id: 'response-time', priority: 'important', item: 'Server response < 500ms', automated: true },
{ id: 'security-headers', priority: 'nice', item: 'Security headers configured', automated: true }
],
crawling: [
{ id: 'robots-txt', priority: 'critical', item: 'robots.txt exists and valid', automated: true },
{ id: 'sitemap', priority: 'critical', item: 'XML Sitemap exists', automated: true },
{ id: 'sitemap-submitted', priority: 'important', item: 'Sitemap submitted to Search Console', automated: false },
{ id: 'no-crawl-errors', priority: 'critical', item: 'No crawl errors in Search Console', automated: false },
{ id: 'canonical-tags', priority: 'critical', item: 'Canonical tags on all pages', automated: true }
],
indexing: [
{ id: 'indexable', priority: 'critical', item: 'Important pages are indexable', automated: true },
{ id: 'noindex-correct', priority: 'important', item: 'noindex only on non-important pages', automated: true },
{ id: 'no-duplicate-content', priority: 'critical', item: 'No duplicate content issues', automated: true },
{ id: 'hreflang', priority: 'important', item: 'hreflang tags for multilingual sites', automated: true }
],
urlStructure: [
{ id: 'url-readable', priority: 'important', item: 'URLs are human-readable', automated: true },
{ id: 'url-lowercase', priority: 'important', item: 'URLs are lowercase', automated: true },
{ id: 'url-hyphens', priority: 'nice', item: 'URLs use hyphens (not underscores)', automated: true },
{ id: 'url-length', priority: 'nice', item: 'URLs under 75 characters', automated: true },
{ id: 'no-parameters', priority: 'nice', item: 'Clean URLs without parameters', automated: true }
],
onPage: [
{ id: 'title-tag', priority: 'critical', item: 'Unique title tag (50-60 chars)', automated: true },
{ id: 'meta-description', priority: 'critical', item: 'Meta description (150-160 chars)', automated: true },
{ id: 'h1-tag', priority: 'critical', item: 'Single H1 per page', automated: true },
{ id: 'heading-hierarchy', priority: 'important', item: 'Proper heading hierarchy', automated: true },
{ id: 'image-alt', priority: 'important', item: 'All images have alt text', automated: true },
{ id: 'internal-links', priority: 'important', item: 'Internal linking structure', automated: false }
],
performance: [
{ id: 'lcp', priority: 'critical', item: 'LCP < 2.5s', automated: true },
{ id: 'inp', priority: 'critical', item: 'INP < 200ms', automated: true },
{ id: 'cls', priority: 'critical', item: 'CLS < 0.1', automated: true },
{ id: 'mobile-friendly', priority: 'critical', item: 'Mobile-friendly design', automated: true },
{ id: 'page-size', priority: 'important', item: 'Page size < 3MB', automated: true }
],
structuredData: [
{ id: 'jsonld', priority: 'important', item: 'JSON-LD structured data', automated: true },
{ id: 'valid-schema', priority: 'important', item: 'Schema validates in Rich Results Test', automated: false },
{ id: 'breadcrumbs', priority: 'nice', item: 'Breadcrumb schema', automated: true }
]
};
// Generate Audit Report
export function generateSEOReport(results: Record<string, boolean>): string {
let report = '# Technical SEO Audit Report\n\n';
Object.entries(technicalSEOChecklist).forEach(([category, items]) => {
report += `## ${category.charAt(0).toUpperCase() + category.slice(1)}\n\n`;
items.forEach(item => {
const status = results[item.id];
const emoji = status ? '✅' : (item.priority === 'critical' ? '❌' : '⚠️');
report += `${emoji} ${item.item}\n`;
});
report += '\n';
});
return report;
}
```
---
## Checklist Summary
| Category | Critical | Important | Nice to Have |
|----------|----------|-----------|--------------|
| **Server** | HTTPS, Redirects | Response time | Security headers |
| **Crawling** | robots.txt, Sitemap | Search Console | Crawl budget |
| **Indexing** | Canonical, No duplicates | hreflang | Coverage report |
| **URLs** | - | Readable, lowercase | Short URLs |
| **On-Page** | Title, Meta, H1 | Alt text, Headings | Schema |
| **Performance** | Core Web Vitals | Page size | Resource hints |
---
## Fazit
Technical SEO Checklist erfordert:
1. **Server-Basics**: HTTPS, Redirects, Security
2. **Crawling**: robots.txt, Sitemap, Canonical
3. **On-Page**: Title, Meta, Headings, Images
4. **Performance**: Core Web Vitals optimieren
Regelmäßige Audits sichern die technische Basis.
---
## Bildprompts
1. "SEO audit dashboard showing checklist items, green checkmarks"
2. "Technical SEO pyramid diagram, layered structure"
3. "Search Console crawl stats visualization, data charts"
---
## Quellen
- [Google Search Central](https://developers.google.com/search/docs)
- [Ahrefs Technical SEO Guide](https://ahrefs.com/blog/technical-seo/)
- [Moz Technical SEO](https://moz.com/learn/seo/technical-seo)
- [Web.dev SEO](https://web.dev/learn/seo/)