feat: enhance SSR support with language extraction, dynamic page titles, and updated styles; adjust color theme

This commit is contained in:
2026-02-26 11:09:42 +00:00
parent 40ffa8207e
commit 965a505e15
8 changed files with 85 additions and 37 deletions

View File

@@ -164,6 +164,12 @@ const { ssrRequest } = require("../lib/ssr-server")
}
var tpl = context.fs.readFile("templates/spa.html")
// Extract language from URL for <html lang="..."> (before other replacements)
var langMatch = url.match(/^\/(de|en)(\/|$)/)
var pageLang = langMatch ? langMatch[1] : "de"
tpl = tpl.replace(/<html lang="[^"]*">/, '<html lang="' + pageLang + '">')
tpl = tpl.replace("<!--HEAD-->", head)
tpl = tpl.replace("<!--HTML-->", html)
tpl = tpl.replace("<!--SSR.ERROR-->", error ? "<!--" + error + "-->" : "")

View File

@@ -1 +1 @@
frontend/spa.html
../../frontend/spa.html

View File

@@ -4,7 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>__PROJECT_TITLE__</title>
<base href="/" />
<link rel="stylesheet" href="/dist/index.css?t=__TIMESTAMP__" />

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { untrack } from "svelte"
import { metricCall } from "./config"
import { location, mobileMenuOpen, currentContentEntry } from "./lib/store"
import { _, locale } from "./lib/i18n/index"
@@ -21,12 +22,13 @@
stripLanguageFromPath,
} from "./lib/i18n"
export let url = ""
let { url = "" }: { url?: string } = $props()
initScrollRestoration()
// SSR: capture initial URL once (not reactive — url is only set server-side)
untrack(() => {
if (url) {
// ssr
let l = url.split("?")
$location = {
path: l[0],
@@ -38,6 +40,7 @@
const lang = extractLanguageFromPath(l[0]) || DEFAULT_LANGUAGE
$locale = lang
}
})
// Redirect root "/" to default language
$effect(() => {
@@ -137,8 +140,25 @@
const routePath = stripLanguageFromPath($location.path)
loadContent(lang, routePath || "/")
})
// Dynamic page title
const SITE_NAME = "Tibi Starter"
let pageTitle = $derived(
contentEntry?.meta?.title
? `${contentEntry.meta.title}${SITE_NAME}`
: contentEntry?.name
? `${contentEntry.name}${SITE_NAME}`
: SITE_NAME
)
</script>
<svelte:head>
<title>{pageTitle}</title>
{#if contentEntry?.meta?.description}
<meta name="description" content={contentEntry.meta.description} />
{/if}
</svelte:head>
<LoadingBar />
<ToastContainer />

View File

@@ -65,9 +65,10 @@
</div>
<!-- Decorative bottom wave -->
<div class="absolute bottom-0 left-0 right-0 z-10">
<svg viewBox="0 0 1440 80" fill="none" class="w-full h-auto">
<path d="M0 80V40C240 10 480 0 720 20C960 40 1200 50 1440 30V80H0Z" fill="white"></path>
<div class="absolute -bottom-px left-0 right-0 z-10">
<svg viewBox="0 0 1440 80" preserveAspectRatio="none" class="block w-full h-auto">
<path d="M0 80V40C240 10 480 0 720 20C960 40 1200 50 1440 30V80H0Z" fill="currentColor" class="text-white"
></path>
</svg>
</div>
</section>

View File

@@ -20,17 +20,17 @@
/* ── Custom theme tokens ─────────────────────────────────────────── */
@theme {
--color-brand-50: #f0f5ff;
--color-brand-100: #e0eaff;
--color-brand-200: #c7d7fe;
--color-brand-300: #a4bcfd;
--color-brand-400: #8098f9;
--color-brand-500: #6172f3;
--color-brand-600: #444ce7;
--color-brand-700: #3538cd;
--color-brand-800: #2d31a6;
--color-brand-900: #2d3282;
--color-brand-950: #1f235b;
--color-brand-50: #fef2f3;
--color-brand-100: #fee2e5;
--color-brand-200: #fecacd;
--color-brand-300: #fda4ab;
--color-brand-400: #fb6e7c;
--color-brand-500: #f14152;
--color-brand-600: #ce2237;
--color-brand-700: #ac1526;
--color-brand-800: #8f1523;
--color-brand-900: #7a1723;
--color-brand-950: #43070f;
--color-accent-400: #f79009;
--color-accent-500: #f59e0b;

View File

@@ -1,5 +1,6 @@
import { addMessages, init } from "svelte-i18n"
import { DEFAULT_LANGUAGE } from "./lib/i18n"
import { render } from "svelte/server"
import { addMessages, init, locale } from "svelte-i18n"
import { DEFAULT_LANGUAGE, extractLanguageFromPath } from "./lib/i18n"
import deLocale from "./lib/i18n/locales/de.json"
import enLocale from "./lib/i18n/locales/en.json"
import App from "./App.svelte"
@@ -8,9 +9,30 @@ import App from "./App.svelte"
addMessages("de", deLocale)
addMessages("en", enLocale)
/**
* SSR render wrapper for Svelte 5.
*
* tibi-server calls `app.default.render({ url })`.
* Svelte 5 no longer provides a `.render()` method on components —
* instead `render()` must be imported from `svelte/server`.
*
* This wrapper keeps the hook-side API compatible while delegating
* to the Svelte 5 render function internally.
*/
export default {
render({ url }: { url: string }) {
const lang = extractLanguageFromPath(url) || DEFAULT_LANGUAGE
init({
fallbackLocale: DEFAULT_LANGUAGE,
initialLocale: DEFAULT_LANGUAGE,
initialLocale: lang,
})
locale.set(lang)
const { body, head } = render(App, {
props: { url },
})
export default App
return { html: body, head }
},
}