Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import type { Metadata, Viewport } from 'next'
|
|
import { headers } from 'next/headers'
|
|
import { de } from '@/lib/i18n/de'
|
|
import { THEME_KEY } from '@/components/ThemeToggle'
|
|
import './globals.css'
|
|
|
|
export const metadata: Metadata = {
|
|
title: { default: de.app.name, template: `%s · ${de.app.shortName}` },
|
|
description: de.app.description,
|
|
robots: { index: false, follow: false },
|
|
}
|
|
|
|
export const viewport: Viewport = {
|
|
themeColor: '#15181A',
|
|
width: 'device-width',
|
|
initialScale: 1,
|
|
}
|
|
|
|
/**
|
|
* Setzt das gewählte Thema, **bevor** das erste Mal gemalt wird.
|
|
*
|
|
* Ohne das blitzt beim Laden kurz das falsche Thema auf. Es ist die eine
|
|
* Stelle mit `dangerouslySetInnerHTML`, und der Inhalt ist eine Konstante aus
|
|
* diesem Modul - kein Nutzertext. Das Verbot aus docs/07 gilt Nutzerinhalten.
|
|
*/
|
|
const THEME_SCRIPT =
|
|
`try{var t=localStorage.getItem(${JSON.stringify(THEME_KEY)});` +
|
|
`if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t)}catch(e){}`
|
|
|
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
// Die strikte Inhaltsrichtlinie erlaubt nur Skripte mit passendem Nonce,
|
|
// siehe middleware.ts.
|
|
const nonce = (await headers()).get('x-nonce') ?? undefined
|
|
|
|
return (
|
|
<html lang="de">
|
|
<head>
|
|
{/*
|
|
`suppressHydrationWarning` wegen des Nonce, nicht wegen des Inhalts.
|
|
Der Browser **leert das Nonce-Attribut**, sobald er das Element
|
|
geparst hat - so schreibt es die CSP-Spezifikation vor, damit kein
|
|
Skript den Wert über `getAttribute` auslesen und sich selbst
|
|
nachladen kann. React vergleicht beim Hydrieren aber genau dieses
|
|
Attribut, liest clientseitig den leeren Wert und meldet einen
|
|
Unterschied, den es nicht gibt: unser Markup ist auf beiden Seiten
|
|
dasselbe.
|
|
|
|
Es bleibt bei einem einzigen Element mit einer Konstante als Inhalt,
|
|
deshalb ist die Unterdrückung hier eng genug. Der Nonce selbst
|
|
entfällt nicht: ohne ihn blockiert die Richtlinie das Skript, und
|
|
ohne das Skript blitzt beim Laden das falsche Thema auf.
|
|
*/}
|
|
<script
|
|
nonce={nonce}
|
|
suppressHydrationWarning
|
|
dangerouslySetInnerHTML={{ __html: THEME_SCRIPT }}
|
|
/>
|
|
{/* Nur die beiden Schnitte vorladen, die auf jeder Seite vorkommen. */}
|
|
<link
|
|
rel="preload"
|
|
href="/fonts/ibm-plex-sans-latin-400-normal.woff2"
|
|
as="font"
|
|
type="font/woff2"
|
|
crossOrigin="anonymous"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
href="/fonts/ibm-plex-mono-latin-400-normal.woff2"
|
|
as="font"
|
|
type="font/woff2"
|
|
crossOrigin="anonymous"
|
|
/>
|
|
</head>
|
|
<body>
|
|
<a
|
|
href="#inhalt"
|
|
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:border focus:border-line focus:bg-panel focus:px-3 focus:py-2"
|
|
>
|
|
{de.nav.skipToContent}
|
|
</a>
|
|
{children}
|
|
</body>
|
|
</html>
|
|
)
|
|
}
|