✨ feat: enhance SSR support with language extraction, dynamic page titles, and updated styles; adjust color theme
This commit is contained in:
@@ -65,7 +65,7 @@ make docker-down
|
||||
# "make help" zeigt alle Kommandos
|
||||
```
|
||||
|
||||
| UI | URL |
|
||||
| UI | URL |
|
||||
| --- | --- |
|
||||
| Website | <https://tibi-svelte-starter.code.testversion.online/> |
|
||||
| Tibi Admin | <https://tibi-svelte-starter-tibiadmin.code.testversion.online/> |
|
||||
|
||||
@@ -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 + "-->" : "")
|
||||
|
||||
@@ -1 +1 @@
|
||||
frontend/spa.html
|
||||
../../frontend/spa.html
|
||||
@@ -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__" />
|
||||
|
||||
|
||||
@@ -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,23 +22,25 @@
|
||||
stripLanguageFromPath,
|
||||
} from "./lib/i18n"
|
||||
|
||||
export let url = ""
|
||||
let { url = "" }: { url?: string } = $props()
|
||||
|
||||
initScrollRestoration()
|
||||
|
||||
if (url) {
|
||||
// ssr
|
||||
let l = url.split("?")
|
||||
$location = {
|
||||
path: l[0],
|
||||
search: l.length > 1 ? l[1] : "",
|
||||
hash: "",
|
||||
push: false,
|
||||
pop: false,
|
||||
// SSR: capture initial URL once (not reactive — url is only set server-side)
|
||||
untrack(() => {
|
||||
if (url) {
|
||||
let l = url.split("?")
|
||||
$location = {
|
||||
path: l[0],
|
||||
search: l.length > 1 ? l[1] : "",
|
||||
hash: "",
|
||||
push: false,
|
||||
pop: false,
|
||||
}
|
||||
const lang = extractLanguageFromPath(l[0]) || DEFAULT_LANGUAGE
|
||||
$locale = lang
|
||||
}
|
||||
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 />
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
init({
|
||||
fallbackLocale: DEFAULT_LANGUAGE,
|
||||
initialLocale: DEFAULT_LANGUAGE,
|
||||
})
|
||||
/**
|
||||
* 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
|
||||
|
||||
export default App
|
||||
init({
|
||||
fallbackLocale: DEFAULT_LANGUAGE,
|
||||
initialLocale: lang,
|
||||
})
|
||||
locale.set(lang)
|
||||
|
||||
const { body, head } = render(App, {
|
||||
props: { url },
|
||||
})
|
||||
|
||||
return { html: body, head }
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user