style: auto-format codebase with Prettier

Applied formatting to 1487+ files using pnpm format:write
  - TypeScript/JavaScript files
  - Svelte components
  - Astro pages
  - JSON configs
  - Markdown docs

  13 files still need manual review (Astro JSX comments)
This commit is contained in:
Wuesteon 2025-11-27 18:33:16 +01:00
parent 0241f5554c
commit d36b321d9d
3952 changed files with 661498 additions and 739751 deletions

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ Basierend auf der Analyse empfehle ich die **PocketBase-Integration (Option 1)**
## Projektziele
- ✅ Content Marketing Platform für SEO & Thought Leadership
- ✅ Nahtlose Integration in bestehendes uload-System
- ✅ Nahtlose Integration in bestehendes uload-System
- ✅ Skalierbar für 100+ Artikel
- ✅ DSGVO-konform
- ✅ Mobile-optimiert
@ -16,75 +16,90 @@ Basierend auf der Analyse empfehle ich die **PocketBase-Integration (Option 1)**
## Timeline & Milestones
### Tag 1: Database & Backend Setup
**Ziel**: Komplette Datenbank-Struktur und API-Endpoints
#### Vormittag (4h)
- [ ] PocketBase Collections erstellen
- [ ] Relationen definieren
- [ ] Validation Rules setzen
- [ ] Test-Daten einfügen
#### Nachmittag (4h)
- [ ] Server-Routes implementieren
- [ ] API-Endpoints testen
- [ ] Error Handling
- [ ] Pagination Logic
### Tag 2: Frontend Basis-Komponenten
**Ziel**: Blog-Übersicht und Artikel-Ansicht funktionsfähig
#### Vormittag (4h)
- [ ] Blog-Übersichtsseite
- [ ] BlogCard Komponente
- [ ] Kategorie-Filter
- [ ] Tag-Cloud
#### Nachmittag (4h)
- [ ] Artikel-Detailseite
- [ ] Reading Progress Bar
- [ ] Table of Contents
- [ ] Share Buttons
### Tag 3: Admin-Interface
**Ziel**: Vollständiges CMS für Blog-Verwaltung
#### Vormittag (4h)
- [ ] Admin-Dashboard
- [ ] Artikel-Liste mit Status
- [ ] Bulk-Actions
- [ ] Suchfunktion
#### Nachmittag (4h)
- [ ] Rich-Text Editor (Tiptap)
- [ ] Media Upload
- [ ] Preview-Funktion
- [ ] Auto-Save
### Tag 4: SEO & Performance
**Ziel**: Optimale Sichtbarkeit und Geschwindigkeit
#### Vormittag (4h)
- [ ] Meta-Tags Management
- [ ] Schema.org Markup
- [ ] XML Sitemap
- [ ] RSS/Atom Feed
#### Nachmittag (4h)
- [ ] Image Optimization
- [ ] Lazy Loading
- [ ] Cache-Strategy
- [ ] CDN-Integration
### Tag 5: Features & Polish
**Ziel**: Premium-Features und finale Optimierungen
#### Vormittag (4h)
- [ ] Verwandte Artikel
- [ ] Lesezeit-Berechnung
- [ ] Newsletter-Integration
- [ ] Social Media Auto-Post
#### Nachmittag (4h)
- [ ] Analytics Dashboard
- [ ] A/B Testing Setup
- [ ] Mobile Optimierung
@ -146,21 +161,21 @@ CREATE TABLE blog_post_tags (
```typescript
// Blog API Routes
GET /api/blog/posts // Liste mit Pagination
GET /api/blog/posts/[slug] // Einzelner Artikel
POST /api/blog/posts/[slug]/view // View Counter
GET /api/blog/categories // Alle Kategorien
GET /api/blog/tags // Alle Tags
GET /api/blog/search // Volltextsuche
GET /api/blog/feed.xml // RSS Feed
GET /api/blog/sitemap.xml // Sitemap
GET / api / blog / posts; // Liste mit Pagination
GET / api / blog / posts / [slug]; // Einzelner Artikel
POST / api / blog / posts / [slug] / view; // View Counter
GET / api / blog / categories; // Alle Kategorien
GET / api / blog / tags; // Alle Tags
GET / api / blog / search; // Volltextsuche
GET / api / blog / feed.xml; // RSS Feed
GET / api / blog / sitemap.xml; // Sitemap
// Admin API Routes (auth required)
POST /api/admin/blog/posts // Neuer Artikel
PUT /api/admin/blog/posts/[id] // Update
DELETE /api/admin/blog/posts/[id] // Löschen
POST /api/admin/blog/posts/[id]/publish // Veröffentlichen
POST /api/admin/blog/upload // Bild-Upload
POST / api / admin / blog / posts; // Neuer Artikel
PUT / api / admin / blog / posts / [id]; // Update
DELETE / api / admin / blog / posts / [id]; // Löschen
POST / api / admin / blog / posts / [id] / publish; // Veröffentlichen
POST / api / admin / blog / upload; // Bild-Upload
```
### Komponenten-Architektur
@ -212,18 +227,21 @@ src/lib/components/blog/
### Content-Kalender (erste 3 Monate)
**Monat 1**: Foundations (4 Artikel/Monat)
- Woche 1: Psychology & UX
- Woche 2: Technical Tutorial
- Woche 3: Case Study
- Woche 4: Industry Trends
**Monat 2**: Deep Dives (6 Artikel/Monat)
- Analytics Deep Dives
- Integration Guides
- Performance Optimization
- Security Best Practices
**Monat 3**: Growth (8 Artikel/Monat)
- Guest Posts
- User Success Stories
- Feature Announcements
@ -234,11 +252,13 @@ src/lib/components/blog/
### Target Keywords
**Primary Keywords** (Schwierigkeit: Mittel)
- "url kürzen" (9.900 Suchen/Monat)
- "link verkürzen" (6.600 Suchen/Monat)
- "kurze urls" (2.400 Suchen/Monat)
**Long-Tail Keywords** (Schwierigkeit: Leicht)
- "kostenlos url kürzen ohne anmeldung"
- "eigene domain für kurze links"
- "qr code mit logo erstellen"
@ -260,12 +280,14 @@ src/lib/components/blog/
## Performance KPIs
### Technical Metrics
- **Page Load**: < 2s (Mobile 3G)
- **Time to Interactive**: < 3.5s
- **Core Web Vitals**: Alle grün
- **Lighthouse Score**: > 90
### Business Metrics
- **Organic Traffic**: +50% in 3 Monaten
- **Conversion Rate**: Blog → Sign-up > 3%
- **Engagement Rate**: > 2 Min Average Time
@ -275,26 +297,29 @@ src/lib/components/blog/
### Potenzielle Risiken & Mitigationen
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|--------|-------------------|---------|------------|
| Editor-Komplexität | Mittel | Hoch | Start mit SimpleMDE, später Upgrade |
| Performance-Probleme | Niedrig | Mittel | Caching-Strategy von Anfang an |
| Content-Erstellung | Hoch | Mittel | Freelancer-Pool aufbauen |
| SEO-Konkurrenz | Mittel | Niedrig | Nischen-Keywords fokussieren |
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
| -------------------- | ------------------ | ------- | ----------------------------------- |
| Editor-Komplexität | Mittel | Hoch | Start mit SimpleMDE, später Upgrade |
| Performance-Probleme | Niedrig | Mittel | Caching-Strategy von Anfang an |
| Content-Erstellung | Hoch | Mittel | Freelancer-Pool aufbauen |
| SEO-Konkurrenz | Mittel | Niedrig | Nischen-Keywords fokussieren |
## Budget & Ressourcen
### Entwicklung (5 Tage)
- **Developer**: 5 Tage × 8h = 40h
- **Design Assets**: Vorhandene UI-Komponenten nutzen
- **Testing**: 1 Tag zusätzlich
### Content (Ongoing)
- **Launch Content**: 5 Artikel (intern)
- **Monthly Content**: 4-8 Artikel
- **Freelancer Budget**: €500-1000/Monat
### Tools & Services
- **Grammarly**: Rechtschreibprüfung
- **Canva Pro**: Grafiken
- **Unsplash+**: Stock-Fotos
@ -305,6 +330,7 @@ src/lib/components/blog/
### Pre-Launch Checklist
#### Funktionalität
- [ ] Alle CRUD-Operationen funktionieren
- [ ] Pagination arbeitet korrekt
- [ ] Suche liefert relevante Ergebnisse
@ -312,24 +338,28 @@ src/lib/components/blog/
- [ ] RSS Feed validiert
#### Performance
- [ ] Lighthouse Audit > 90
- [ ] Mobile-Responsive
- [ ] Bilder optimiert
- [ ] Caching aktiviert
#### SEO
- [ ] Meta-Tags vollständig
- [ ] Sitemap generiert
- [ ] Schema.org implementiert
- [ ] Social Cards funktionieren
#### Security
- [ ] Input-Validation
- [ ] XSS-Protection
- [ ] CSRF-Token
- [ ] Rate Limiting
#### Accessibility
- [ ] WCAG 2.1 AA konform
- [ ] Keyboard-Navigation
- [ ] Screen-Reader kompatibel
@ -338,18 +368,21 @@ src/lib/components/blog/
## Post-Launch Plan
### Woche 1
- Monitoring Setup
- Bug Fixes
- Performance Tuning
- Erste Analytics
### Monat 1
- Content-Pipeline etablieren
- SEO-Optimierungen
- User-Feedback sammeln
- A/B Tests starten
### Quartal 1
- Feature-Erweiterungen
- Newsletter-Integration
- Kommentar-System
@ -358,16 +391,19 @@ src/lib/components/blog/
## Success Metrics
### Launch (Tag 1)
- ✅ Blog live und funktional
- ✅ 5 Launch-Artikel online
- ✅ Keine kritischen Bugs
### Monat 1
- ✅ 20+ Blog-Artikel
- ✅ 1000+ Unique Visitors
- ✅ 5+ Backlinks
### Quartal 1
- ✅ 50+ Blog-Artikel
- ✅ 10.000+ Monthly Visitors
- ✅ Top 10 Rankings für Target Keywords
@ -394,4 +430,4 @@ src/lib/components/blog/
**Empfehlung**: Sofortiger Start mit der PocketBase-Integration. Der vorgeschlagene 5-Tage-Plan ist realistisch und liefert ein production-ready Blog-System, das perfekt in die bestehende uload-Architektur passt.
**Kontakt für Rückfragen**: Bei Fragen zur Implementierung oder für Detail-Diskussionen stehe ich zur Verfügung.
**Kontakt für Rückfragen**: Bei Fragen zur Implementierung oder für Detail-Diskussionen stehe ich zur Verfügung.

View file

@ -3,7 +3,9 @@
## Übersicht: 3 Ansätze für statische Markdown-Blogs
### 1. **mdsvex** - Markdown als Svelte-Komponenten (Empfohlen)
### 2. **Vite Glob Import** - Dynamisches Laden zur Build-Zeit
### 2. **Vite Glob Import** - Dynamisches Laden zur Build-Zeit
### 3. **Content Collections** - Strukturierte Markdown-Verwaltung
---
@ -26,7 +28,7 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit()],
});
```
@ -41,23 +43,20 @@ import rehypeAutolinkHeadings from 'rehype-autolink-headings';
/** @type {import('mdsvex').MdsvexOptions} */
const mdsvexOptions = {
extensions: ['.md', '.mdx'],
layout: {
blog: './src/lib/layouts/BlogLayout.svelte'
},
rehypePlugins: [
rehypeSlug,
[rehypeAutolinkHeadings, { behavior: 'wrap' }]
]
extensions: ['.md', '.mdx'],
layout: {
blog: './src/lib/layouts/BlogLayout.svelte',
},
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }]],
};
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte', '.md', '.mdx'],
preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)],
kit: {
adapter: adapter()
}
extensions: ['.svelte', '.md', '.mdx'],
preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)],
kit: {
adapter: adapter(),
},
};
export default config;
@ -68,54 +67,54 @@ export default config;
```svelte
<!-- src/lib/layouts/BlogLayout.svelte -->
<script>
export let title = '';
export let date = '';
export let author = '';
export let excerpt = '';
export let tags = [];
export let image = '';
let formattedDate = $derived(
new Date(date).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
);
export let title = '';
export let date = '';
export let author = '';
export let excerpt = '';
export let tags = [];
export let image = '';
let formattedDate = $derived(
new Date(date).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
);
</script>
<svelte:head>
<title>{title} | uload Blog</title>
<meta name="description" content={excerpt} />
<meta property="og:title" content={title} />
<meta property="og:description" content={excerpt} />
{#if image}
<meta property="og:image" content={image} />
{/if}
<title>{title} | uload Blog</title>
<meta name="description" content={excerpt} />
<meta property="og:title" content={title} />
<meta property="og:description" content={excerpt} />
{#if image}
<meta property="og:image" content={image} />
{/if}
</svelte:head>
<article class="prose prose-lg mx-auto px-4 py-8 max-w-4xl">
<header class="mb-8">
<h1 class="text-4xl font-bold mb-2">{title}</h1>
<div class="text-gray-600 flex gap-4 items-center">
<time datetime={date}>{formattedDate}</time>
<span></span>
<span>{author}</span>
</div>
{#if tags.length > 0}
<div class="flex gap-2 mt-4">
{#each tags as tag}
<span class="bg-gray-100 px-3 py-1 rounded-full text-sm">
{tag}
</span>
{/each}
</div>
{/if}
</header>
<div class="content">
<slot />
</div>
<header class="mb-8">
<h1 class="text-4xl font-bold mb-2">{title}</h1>
<div class="text-gray-600 flex gap-4 items-center">
<time datetime={date}>{formattedDate}</time>
<span></span>
<span>{author}</span>
</div>
{#if tags.length > 0}
<div class="flex gap-2 mt-4">
{#each tags as tag}
<span class="bg-gray-100 px-3 py-1 rounded-full text-sm">
{tag}
</span>
{/each}
</div>
{/if}
</header>
<div class="content">
<slot />
</div>
</article>
```
@ -162,52 +161,52 @@ src/routes/
```javascript
// src/routes/blog/+page.js
export async function load() {
const posts = import.meta.glob('/src/content/blog/*.md');
const postPromises = Object.entries(posts).map(async ([path, resolver]) => {
const { metadata } = await resolver();
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata
};
});
const allPosts = await Promise.all(postPromises);
// Nach Datum sortieren
allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
return {
posts: allPosts
};
const posts = import.meta.glob('/src/content/blog/*.md');
const postPromises = Object.entries(posts).map(async ([path, resolver]) => {
const { metadata } = await resolver();
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata,
};
});
const allPosts = await Promise.all(postPromises);
// Nach Datum sortieren
allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
return {
posts: allPosts,
};
}
```
```svelte
<!-- src/routes/blog/+page.svelte -->
<script>
let { data } = $props();
let { data } = $props();
</script>
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">Blog</h1>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{#each data.posts as post}
<article class="border rounded-lg p-6 hover:shadow-lg transition">
<h2 class="text-xl font-semibold mb-2">
<a href="/blog/{post.slug}" class="hover:text-blue-600">
{post.title}
</a>
</h2>
<p class="text-gray-600 mb-4">{post.excerpt}</p>
<div class="text-sm text-gray-500">
{new Date(post.date).toLocaleDateString('de-DE')}
</div>
</article>
{/each}
</div>
<h1 class="text-3xl font-bold mb-8">Blog</h1>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{#each data.posts as post}
<article class="border rounded-lg p-6 hover:shadow-lg transition">
<h2 class="text-xl font-semibold mb-2">
<a href="/blog/{post.slug}" class="hover:text-blue-600">
{post.title}
</a>
</h2>
<p class="text-gray-600 mb-4">{post.excerpt}</p>
<div class="text-sm text-gray-500">
{new Date(post.date).toLocaleDateString('de-DE')}
</div>
</article>
{/each}
</div>
</div>
```
@ -231,13 +230,13 @@ import { marked } from 'marked';
import matter from 'gray-matter';
export function parseMarkdown(content) {
const { data, content: markdown } = matter(content);
const html = marked(markdown);
return {
metadata: data,
html
};
const { data, content: markdown } = matter(content);
const html = marked(markdown);
return {
metadata: data,
html,
};
}
```
@ -248,28 +247,28 @@ export function parseMarkdown(content) {
import { parseMarkdown } from '$lib/utils/markdown';
export async function load() {
const postFiles = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default'
});
const posts = await Promise.all(
Object.entries(postFiles).map(async ([path, resolver]) => {
const content = await resolver();
const { metadata, html } = parseMarkdown(content);
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata,
content: html
};
})
);
return {
posts: posts.sort((a, b) => new Date(b.date) - new Date(a.date))
};
const postFiles = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default',
});
const posts = await Promise.all(
Object.entries(postFiles).map(async ([path, resolver]) => {
const content = await resolver();
const { metadata, html } = parseMarkdown(content);
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata,
content: html,
};
})
);
return {
posts: posts.sort((a, b) => new Date(b.date) - new Date(a.date)),
};
}
```
@ -281,37 +280,37 @@ import { parseMarkdown } from '$lib/utils/markdown';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
try {
const post = await import(`../../../content/blog/${params.slug}.md?raw`);
const { metadata, html } = parseMarkdown(post.default);
return {
...metadata,
content: html,
slug: params.slug
};
} catch (e) {
throw error(404, 'Post nicht gefunden');
}
try {
const post = await import(`../../../content/blog/${params.slug}.md?raw`);
const { metadata, html } = parseMarkdown(post.default);
return {
...metadata,
content: html,
slug: params.slug,
};
} catch (e) {
throw error(404, 'Post nicht gefunden');
}
}
```
```svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
let { data } = $props();
let { data } = $props();
</script>
<svelte:head>
<title>{data.title}</title>
<meta name="description" content={data.excerpt} />
<title>{data.title}</title>
<meta name="description" content={data.excerpt} />
</svelte:head>
<article class="prose mx-auto px-4 py-8">
<h1>{data.title}</h1>
<time>{new Date(data.date).toLocaleDateString('de-DE')}</time>
{@html data.content}
<h1>{data.title}</h1>
<time>{new Date(data.date).toLocaleDateString('de-DE')}</time>
{@html data.content}
</article>
```
@ -340,20 +339,20 @@ src/content/
import { z } from 'zod';
export const blogSchema = z.object({
title: z.string(),
date: z.date(),
author: z.string(),
excerpt: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
draft: z.boolean().default(false)
title: z.string(),
date: z.date(),
author: z.string(),
excerpt: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
draft: z.boolean().default(false),
});
export const collections = {
blog: {
schema: blogSchema,
directory: 'src/content/blog'
}
blog: {
schema: blogSchema,
directory: 'src/content/blog',
},
};
```
@ -366,45 +365,45 @@ import matter from 'gray-matter';
import { marked } from 'marked';
export async function getCollection(collection) {
const posts = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default'
});
const entries = await Promise.all(
Object.entries(posts).map(async ([path, resolver]) => {
// Drafts überspringen
if (path.includes('_drafts')) return null;
const content = await resolver();
const { data, content: markdown } = matter(content);
// Schema validieren
const metadata = blogSchema.parse({
...data,
date: new Date(data.date)
});
// Draft-Posts in Production ausblenden
if (metadata.draft && import.meta.env.PROD) return null;
const slug = path.split('/').pop().replace('.md', '');
const html = marked(markdown);
return {
slug,
...metadata,
content: html
};
})
);
return entries.filter(Boolean);
const posts = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default',
});
const entries = await Promise.all(
Object.entries(posts).map(async ([path, resolver]) => {
// Drafts überspringen
if (path.includes('_drafts')) return null;
const content = await resolver();
const { data, content: markdown } = matter(content);
// Schema validieren
const metadata = blogSchema.parse({
...data,
date: new Date(data.date),
});
// Draft-Posts in Production ausblenden
if (metadata.draft && import.meta.env.PROD) return null;
const slug = path.split('/').pop().replace('.md', '');
const html = marked(markdown);
return {
slug,
...metadata,
content: html,
};
})
);
return entries.filter(Boolean);
}
export async function getEntry(collection, slug) {
const posts = await getCollection(collection);
return posts.find(post => post.slug === slug);
const posts = await getCollection(collection);
return posts.find((post) => post.slug === slug);
}
```
@ -419,31 +418,35 @@ export async function getEntry(collection, slug) {
import { getCollection } from '$lib/content';
export async function GET() {
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>uload Blog</title>
<link>${site}/blog</link>
<description>Insights über URLs, Marketing und Psychologie</description>
${posts.map(post => `
${posts
.map(
(post) => `
<item>
<title>${post.title}</title>
<link>${site}/blog/${post.slug}</link>
<description>${post.excerpt}</description>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
</item>
`).join('')}
`
)
.join('')}
</channel>
</rss>`;
return new Response(xml, {
headers: {
'Content-Type': 'application/xml'
}
});
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
},
});
}
```
@ -452,26 +455,30 @@ export async function GET() {
```javascript
// src/routes/sitemap.xml/+server.js
export async function GET() {
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${posts.map(post => `
${posts
.map(
(post) => `
<url>
<loc>${site}/blog/${post.slug}</loc>
<lastmod>${new Date(post.date).toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
`).join('')}
`
)
.join('')}
</urlset>`;
return new Response(xml, {
headers: {
'Content-Type': 'application/xml'
}
});
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
},
});
}
```
@ -486,10 +493,10 @@ npm install -D shiki
import { codeToHtml } from 'shiki';
export async function highlightCode(code, lang = 'javascript') {
return await codeToHtml(code, {
lang,
theme: 'github-dark'
});
return await codeToHtml(code, {
lang,
theme: 'github-dark',
});
}
```
@ -498,15 +505,15 @@ export async function highlightCode(code, lang = 'javascript') {
```javascript
// src/lib/utils/reading-time.js
export function calculateReadingTime(content) {
const wordsPerMinute = 200;
const words = content.split(/\s+/).length;
const minutes = Math.ceil(words / wordsPerMinute);
return {
minutes,
words,
text: `${minutes} Min. Lesezeit`
};
const wordsPerMinute = 200;
const words = content.split(/\s+/).length;
const minutes = Math.ceil(words / wordsPerMinute);
return {
minutes,
words,
text: `${minutes} Min. Lesezeit`,
};
}
```
@ -515,19 +522,19 @@ export function calculateReadingTime(content) {
```javascript
// src/lib/utils/toc.js
export function extractHeadings(html) {
const regex = /<h([2-3])[^>]*id="([^"]*)"[^>]*>(.*?)<\/h\1>/g;
const headings = [];
let match;
while ((match = regex.exec(html)) !== null) {
headings.push({
level: parseInt(match[1]),
id: match[2],
text: match[3].replace(/<[^>]*>/g, '')
});
}
return headings;
const regex = /<h([2-3])[^>]*id="([^"]*)"[^>]*>(.*?)<\/h\1>/g;
const headings = [];
let match;
while ((match = regex.exec(html)) !== null) {
headings.push({
level: parseInt(match[1]),
id: match[2],
text: match[3].replace(/<[^>]*>/g, ''),
});
}
return headings;
}
```
@ -546,17 +553,17 @@ export const prerender = true; // Statisch zur Build-Zeit generieren
```svelte
<script>
import { inview } from 'svelte-inview';
let isInView = $state(false);
import { inview } from 'svelte-inview';
let isInView = $state(false);
</script>
<div use:inview on:inview_enter={() => isInView = true}>
{#if isInView}
<img src={image} alt={alt} loading="lazy" />
{:else}
<div class="skeleton h-64 bg-gray-200" />
{/if}
<div use:inview on:inview_enter={() => (isInView = true)}>
{#if isInView}
<img src={image} {alt} loading="lazy" />
{:else}
<div class="skeleton h-64 bg-gray-200" />
{/if}
</div>
```
@ -568,16 +575,16 @@ const cache = new Map();
const CACHE_DURATION = 1000 * 60 * 5; // 5 Minuten
export function getCached(key, fetcher) {
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
const data = fetcher();
cache.set(key, { data, timestamp: Date.now() });
return data;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
const data = fetcher();
cache.set(key, { data, timestamp: Date.now() });
return data;
}
```
@ -590,16 +597,16 @@ export function getCached(key, fetcher) {
```javascript
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'markdown': ['marked', 'gray-matter'],
'highlight': ['shiki']
}
}
}
}
build: {
rollupOptions: {
output: {
manualChunks: {
markdown: ['marked', 'gray-matter'],
highlight: ['shiki'],
},
},
},
},
});
```
@ -614,17 +621,17 @@ npm install -D @sveltejs/adapter-static
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: null,
precompress: true
}),
prerender: {
entries: ['*'] // Alle Seiten prerendern
}
}
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: null,
precompress: true,
}),
prerender: {
entries: ['*'], // Alle Seiten prerendern
},
},
};
```
@ -692,4 +699,4 @@ Besuche `http://localhost:5173/blog`
---
**Empfehlung**: Starte mit **mdsvex** (Ansatz 1) - es bietet die beste Balance zwischen Einfachheit und Features, plus du kannst Svelte-Components direkt in Markdown verwenden!
**Empfehlung**: Starte mit **mdsvex** (Ansatz 1) - es bietet die beste Balance zwischen Einfachheit und Features, plus du kannst Svelte-Components direkt in Markdown verwenden!

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ In einer Welt, in der über 60% des Web-Traffics von mobilen Geräten kommt, sin
Die Cognitive Load Theory erklärt, warum kurze URLs so effektiv sind. Unser Gehirn ist darauf programmiert, Energie zu sparen es ist evolutionär faul, aber auf eine intelligente Weise. Bei der Verarbeitung von Informationen sucht es immer nach dem Weg des geringsten Widerstands.
Wenn wir einen kurzen, klaren Link sehen, kann unser Gehirn ihn schnell verarbeiten und kategorisieren. Diese mühelose Verarbeitung erzeugt ein positives Gefühl wir verbinden "einfach" automatisch mit "sicher" und "vertrauenswürdig".
Wenn wir einen kurzen, klaren Link sehen, kann unser Gehirn ihn schnell verarbeiten und kategorisieren. Diese mühelose Verarbeitung erzeugt ein positives Gefühl wir verbinden "einfach" automatisch mit "sicher" und "vertrauenswürdig".
### Der Halo-Effekt kurzer URLs
@ -43,6 +43,7 @@ Unsere Analyse von über 10.000 Link-Klicks hat vier Hauptfaktoren identifiziert
### 1. Erkennbare Domain (60% Wichtigkeit)
Menschen wollen wissen, wo sie landen werden. Eine klare, erkennbare Domain ist der wichtigste Vertrauensfaktor. Das bedeutet:
- Verwenden Sie Ihre Marken-Domain wenn möglich
- Bei Kurz-URLs: Wählen Sie einen Service mit gutem Ruf
- Vermeiden Sie obskure URL-Shortener
@ -50,6 +51,7 @@ Menschen wollen wissen, wo sie landen werden. Eine klare, erkennbare Domain ist
### 2. Keine kryptischen Zeichen (25% Wichtigkeit)
Zufällige Zahlen-Buchstaben-Kombinationen wie "x7h9k2p" schrecken Nutzer ab. Stattdessen:
- Nutzen Sie sprechende Begriffe
- Verwenden Sie relevante Keywords
- Halten Sie es lesbar und merkbar
@ -57,6 +59,7 @@ Zufällige Zahlen-Buchstaben-Kombinationen wie "x7h9k2p" schrecken Nutzer ab. St
### 3. Optimale Länge (10% Wichtigkeit)
Die magische Grenze liegt bei etwa 50 Zeichen. Alles darüber hinaus wird als zu lang wahrgenommen. Studien zeigen:
- 15-30 Zeichen: Optimal für Social Media
- 30-50 Zeichen: Ideal für E-Mail-Marketing
- Über 50 Zeichen: Deutlicher Rückgang der Klickrate
@ -81,6 +84,7 @@ Sonderzeichen wie %, &, = oder ? in der sichtbaren URL verwirren Nutzer und ersc
### 3. Die 50-Zeichen-Regel
Halten Sie Ihre URLs unter 50 Zeichen. Das ist:
- Kurz genug für Twitter/X
- Lesbar auf Mobilgeräten
- Merkbar für Nutzer
@ -89,12 +93,14 @@ Halten Sie Ihre URLs unter 50 Zeichen. Das ist:
### 4. A/B-Testing ist Ihr Freund
Testen Sie verschiedene URL-Varianten:
- Kurz vs. deskriptiv
- Mit Markenname vs. ohne
- Verschiedene Keywords
- Unterschiedliche Strukturen
Messen Sie dabei:
- Klickrate (CTR)
- Conversion Rate
- Bounce Rate
@ -103,6 +109,7 @@ Messen Sie dabei:
### 5. Performance-Tracking implementieren
Ohne Daten keine Optimierung. Moderne Link-Management-Tools bieten:
- Detaillierte Klick-Statistiken
- Geografische Verteilung
- Geräteerkennung
@ -112,18 +119,22 @@ Ohne Daten keine Optimierung. Moderne Link-Management-Tools bieten:
## Psychologische Trigger in URLs nutzen
### Urgency (Dringlichkeit)
- `ulo.ad/flash-sale-24h`
- `ulo.ad/limited-offer`
### Curiosity (Neugier)
- `ulo.ad/geheimtipp-2024`
- `ulo.ad/insider-trick`
### Value (Wert)
- `ulo.ad/gratis-guide`
- `ulo.ad/50-prozent-rabatt`
### Social Proof
- `ulo.ad/bestseller-2024`
- `ulo.ad/meistgelesen`
@ -139,7 +150,8 @@ Zu viele technische Parameter lassen einen Link "unmenschlich" wirken. Nutzer sp
### Mobile Usability Disaster
Auf Smartphones werden lange URLs oft abgeschnitten oder umbrechen. Das Resultat:
Auf Smartphones werden lange URLs oft abgeschnitten oder umbrechen. Das Resultat:
- Unleserlichkeit
- Professioneller Eindruck geht verloren
- Nutzer können das Ziel nicht einschätzen
@ -150,6 +162,7 @@ Auf Smartphones werden lange URLs oft abgeschnitten oder umbrechen. Das Resultat
### E-Commerce: 67% mehr Conversions
Ein großer Online-Händler verkürzte seine Produkt-URLs von durchschnittlich 120 auf 45 Zeichen. Das Ergebnis:
- 67% höhere Conversion Rate
- 42% mehr Social Shares
- 31% niedrigere Bounce Rate
@ -157,6 +170,7 @@ Ein großer Online-Händler verkürzte seine Produkt-URLs von durchschnittlich 1
### Newsletter-Marketing: Verdoppelte Klickrate
Ein B2B-Unternehmen wechselte von langen Tracking-URLs zu personalisierten Kurz-URLs:
- Vorher: `company.com/newsletter/2024/march/article-5?utm_source=email&utm_medium=newsletter&subscriber=12345`
- Nachher: `co.link/cloud-guide`
- Resultat: 2,1x höhere Klickrate
@ -164,6 +178,7 @@ Ein B2B-Unternehmen wechselte von langen Tracking-URLs zu personalisierten Kurz-
### Social Media: 3x mehr Engagement
Ein Influencer nutzte branded Short-URLs statt generischer Affiliate-Links:
- Engagement stieg um 300%
- Trust-Score verbesserte sich um 85%
- Follower-Wachstum +45%
@ -173,6 +188,7 @@ Ein Influencer nutzte branded Short-URLs statt generischer Affiliate-Links:
### KI-optimierte Personalisierung
Moderne Systeme nutzen KI, um für jeden Nutzer die optimale URL-Variante zu generieren basierend auf:
- Demografischen Daten
- Bisherigem Klickverhalten
- Kontext der Interaktion
@ -181,6 +197,7 @@ Moderne Systeme nutzen KI, um für jeden Nutzer die optimale URL-Variante zu gen
### Voice-First Optimization
Mit dem Aufstieg von Sprachassistenten werden "sprechbare" URLs wichtiger:
- Einfache Wörter statt Buchstaben-Zahlen-Kombinationen
- Vermeidung ähnlich klingender Begriffe
- Klare, eindeutige Aussprache
@ -233,6 +250,7 @@ Die Psychologie kurzer URLs ist keine Raketenwissenschaft, aber ihre Auswirkunge
### Ein Werkzeug, das hilft
Tools wie [uload](https://ulo.ad) wurden speziell entwickelt, um die Erkenntnisse der URL-Psychologie in die Praxis umzusetzen. Mit Features wie:
- Personalisierte Kurz-URLs
- Detaillierte Analytics
- A/B-Testing
@ -246,10 +264,11 @@ können Sie sofort damit beginnen, Ihre Link-Performance zu optimieren.
**Über diesen Artikel**: Basierend auf aktuellen Studien zur Nutzerpsychologie und realen Performance-Daten von über 10 Millionen Link-Klicks. Die präsentierten Strategien wurden in der Praxis getestet und validiert.
**Weiterführende Ressourcen**:
- [Cognitive Load Theory in UX Design](https://www.nngroup.com/articles/minimize-cognitive-load/)
- [The Psychology of Web Performance](https://www.smashingmagazine.com/2024/01/psychology-web-performance/)
- [Mobile-First URL Strategy Guide](https://moz.com/blog/mobile-first-indexing)
---
*Haben Sie Fragen oder möchten Sie Ihre eigenen Erfahrungen teilen? Kontaktieren Sie uns oder hinterlassen Sie einen Kommentar. Wir freuen uns auf den Austausch!*
_Haben Sie Fragen oder möchten Sie Ihre eigenen Erfahrungen teilen? Kontaktieren Sie uns oder hinterlassen Sie einen Kommentar. Wir freuen uns auf den Austausch!_