feat(spa): detail-route auf prerender + ssr=true
Lokaler override des global ssr=false. entries() liest aus snapshot/output/index.json, load() pro-slug aus posts/<slug>.json. runtime-fallback bleibt fuer slugs ausserhalb des snapshots. @types/node als devDependency ergaenzt, da node:fs/promises-Typen fuer den SSR-Pfad benoetigt werden. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3fa85fcb07
commit
b5772b8aa2
|
|
@ -20,6 +20,7 @@
|
||||||
"@sveltejs/kit": "^2.57.0",
|
"@sveltejs/kit": "^2.57.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
||||||
"@testing-library/svelte": "^5.3.1",
|
"@testing-library/svelte": "^5.3.1",
|
||||||
|
"@types/node": "^25.6.0",
|
||||||
"jsdom": "^29.0.2",
|
"jsdom": "^29.0.2",
|
||||||
"svelte": "^5.55.2",
|
"svelte": "^5.55.2",
|
||||||
"svelte-check": "^4.4.6",
|
"svelte-check": "^4.4.6",
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,78 @@
|
||||||
import { error, redirect } from '@sveltejs/kit';
|
import { error, redirect } from '@sveltejs/kit'
|
||||||
import { parseLegacyUrl, canonicalPostPath } from '$lib/url/legacy';
|
import { parseLegacyUrl, canonicalPostPath } from '$lib/url/legacy'
|
||||||
import type { PageLoad } from './$types';
|
import type { EntryGenerator, PageLoad } from './$types'
|
||||||
|
import { browser } from '$app/environment'
|
||||||
|
|
||||||
|
export const ssr = true
|
||||||
|
export const prerender = true
|
||||||
|
export const trailingSlash = 'always'
|
||||||
|
|
||||||
|
interface SnapshotIndex {
|
||||||
|
posts: Array<{ slug: string; lang: string; title: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PostJson {
|
||||||
|
slug: string
|
||||||
|
event_id: string
|
||||||
|
created_at: number
|
||||||
|
published_at: number
|
||||||
|
title: string
|
||||||
|
summary: string
|
||||||
|
lang: string
|
||||||
|
cover_image: { url: string; alt?: string; width?: number; height?: number; mime?: string } | null
|
||||||
|
content_markdown: string
|
||||||
|
tags: string[]
|
||||||
|
naddr: string
|
||||||
|
habla_url: string
|
||||||
|
translations: Array<{ lang: string; slug: string; title: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedIndex: SnapshotIndex | undefined
|
||||||
|
async function readIndex(): Promise<SnapshotIndex> {
|
||||||
|
if (cachedIndex) return cachedIndex
|
||||||
|
const fs = await import('node:fs/promises')
|
||||||
|
const path = await import('node:path')
|
||||||
|
const dir = path.resolve('../snapshot/output')
|
||||||
|
const text = await fs.readFile(path.join(dir, 'index.json'), 'utf-8')
|
||||||
|
cachedIndex = JSON.parse(text) as SnapshotIndex
|
||||||
|
return cachedIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readPost(slug: string): Promise<PostJson | undefined> {
|
||||||
|
try {
|
||||||
|
const fs = await import('node:fs/promises')
|
||||||
|
const path = await import('node:path')
|
||||||
|
const dir = path.resolve('../snapshot/output')
|
||||||
|
const text = await fs.readFile(path.join(dir, 'posts', `${slug}.json`), 'utf-8')
|
||||||
|
return JSON.parse(text) as PostJson
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const entries: EntryGenerator = async () => {
|
||||||
|
const idx = await readIndex()
|
||||||
|
return idx.posts.map((p) => ({ slug: p.slug }))
|
||||||
|
}
|
||||||
|
|
||||||
export const load: PageLoad = async ({ url }) => {
|
export const load: PageLoad = async ({ url }) => {
|
||||||
const pathname = url.pathname;
|
const pathname = url.pathname
|
||||||
|
|
||||||
// Legacy-Form /YYYY/MM/DD/<dtag>.html/ → Redirect auf /<dtag>/
|
const legacyDtag = parseLegacyUrl(pathname)
|
||||||
const legacyDtag = parseLegacyUrl(pathname);
|
|
||||||
if (legacyDtag) {
|
if (legacyDtag) {
|
||||||
throw redirect(301, canonicalPostPath(legacyDtag));
|
throw redirect(301, canonicalPostPath(legacyDtag))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kanonisch: /<dtag>/ — erster Segment des Pfades.
|
const segments = pathname.replace(/^\/+|\/+$/g, '').split('/')
|
||||||
const segments = pathname.replace(/^\/+|\/+$/g, '').split('/');
|
|
||||||
if (segments.length !== 1 || !segments[0]) {
|
if (segments.length !== 1 || !segments[0]) {
|
||||||
throw error(404, 'Seite nicht gefunden');
|
throw error(404, 'Seite nicht gefunden')
|
||||||
|
}
|
||||||
|
const dtag = decodeURIComponent(segments[0])
|
||||||
|
|
||||||
|
if (!browser) {
|
||||||
|
const snapshot = await readPost(dtag)
|
||||||
|
if (snapshot) return { dtag, snapshot }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { dtag: decodeURIComponent(segments[0]) };
|
return { dtag, snapshot: null }
|
||||||
};
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue