@nuxt/nitro-server-nightly 4.3.0-29465977.c4f46c64 → 4.3.0-29466372.7f503428
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +177 -177
- package/dist/index.mjs +664 -835
- package/dist/runtime/handlers/{error.d.ts → error.d.mts} +1 -1
- package/dist/runtime/handlers/error.mjs +77 -0
- package/dist/runtime/handlers/island.d.mts +2 -0
- package/dist/runtime/handlers/island.mjs +120 -0
- package/dist/runtime/handlers/renderer.d.mts +2 -0
- package/dist/runtime/handlers/renderer.mjs +298 -0
- package/dist/runtime/middleware/no-ssr.d.mts +2 -0
- package/dist/runtime/middleware/no-ssr.mjs +7 -0
- package/dist/runtime/plugins/dev-server-logs.d.mts +2 -0
- package/dist/runtime/plugins/dev-server-logs.mjs +94 -0
- package/dist/runtime/templates/error-500.mjs +15 -0
- package/dist/runtime/utils/app-config.d.mts +3 -0
- package/dist/runtime/utils/app-config.mjs +31 -0
- package/dist/runtime/utils/cache.d.mts +5 -0
- package/dist/runtime/utils/{cache.js → cache.mjs} +13 -12
- package/dist/runtime/utils/config.d.mts +1 -0
- package/dist/runtime/utils/{dev.d.ts → dev.d.mts} +1 -1
- package/dist/runtime/utils/{dev.js → dev.mjs} +15 -27
- package/dist/runtime/utils/error.d.mts +6 -0
- package/dist/runtime/utils/error.mjs +15 -0
- package/dist/runtime/utils/paths.mjs +19 -0
- package/dist/runtime/utils/renderer/{app.d.ts → app.d.mts} +4 -4
- package/dist/runtime/utils/renderer/app.mjs +39 -0
- package/dist/runtime/utils/renderer/build-files.d.mts +18 -0
- package/dist/runtime/utils/renderer/build-files.mjs +100 -0
- package/dist/runtime/utils/renderer/{inline-styles.d.ts → inline-styles.d.mts} +1 -1
- package/dist/runtime/utils/renderer/inline-styles.mjs +13 -0
- package/dist/runtime/utils/renderer/{islands.d.ts → islands.d.mts} +5 -5
- package/dist/runtime/utils/renderer/islands.mjs +87 -0
- package/dist/runtime/utils/renderer/payload.d.mts +24 -0
- package/dist/runtime/utils/renderer/payload.mjs +64 -0
- package/package.json +8 -8
- package/dist/index.d.ts +0 -185
- package/dist/runtime/handlers/error.js +0 -65
- package/dist/runtime/handlers/island.d.ts +0 -4
- package/dist/runtime/handlers/island.js +0 -101
- package/dist/runtime/handlers/renderer.d.ts +0 -2
- package/dist/runtime/handlers/renderer.js +0 -237
- package/dist/runtime/middleware/no-ssr.d.ts +0 -2
- package/dist/runtime/middleware/no-ssr.js +0 -7
- package/dist/runtime/plugins/dev-server-logs.d.ts +0 -3
- package/dist/runtime/plugins/dev-server-logs.js +0 -82
- package/dist/runtime/templates/error-500.js +0 -6
- package/dist/runtime/utils/app-config.d.ts +0 -3
- package/dist/runtime/utils/app-config.js +0 -25
- package/dist/runtime/utils/cache-driver.d.ts +0 -6
- package/dist/runtime/utils/cache.d.ts +0 -8
- package/dist/runtime/utils/config.d.ts +0 -1
- package/dist/runtime/utils/error.d.ts +0 -6
- package/dist/runtime/utils/error.js +0 -11
- package/dist/runtime/utils/paths.js +0 -16
- package/dist/runtime/utils/renderer/app.js +0 -34
- package/dist/runtime/utils/renderer/build-files.d.ts +0 -16
- package/dist/runtime/utils/renderer/build-files.js +0 -86
- package/dist/runtime/utils/renderer/inline-styles.js +0 -13
- package/dist/runtime/utils/renderer/islands.js +0 -82
- package/dist/runtime/utils/renderer/payload.d.ts +0 -24
- package/dist/runtime/utils/renderer/payload.js +0 -67
- /package/dist/runtime/templates/{error-500.d.ts → error-500.d.mts} +0 -0
- /package/dist/runtime/utils/{config.js → config.mjs} +0 -0
- /package/dist/runtime/utils/{paths.d.ts → paths.d.mts} +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { joinURL, withQuery, withoutBase } from "ufo";
|
|
2
|
+
import { appendResponseHeader, getRequestHeaders, send, setResponseHeader, setResponseHeaders, setResponseStatus } from "h3";
|
|
3
|
+
import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
|
|
4
|
+
import { isJsonRequest } from "../utils/error.mjs";
|
|
5
|
+
import { generateErrorOverlayHTML } from "../utils/dev.mjs";
|
|
6
|
+
export default (async function errorhandler(error, event, { defaultHandler }) {
|
|
7
|
+
if (event.handled || isJsonRequest(event)) {
|
|
8
|
+
// let Nitro handle JSON errors
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// invoke default Nitro error handler (which will log appropriately if required)
|
|
12
|
+
const defaultRes = await defaultHandler(error, event, { json: true });
|
|
13
|
+
// let Nitro handle redirect if appropriate
|
|
14
|
+
const status = error.status || error.statusCode || 500;
|
|
15
|
+
if (status === 404 && defaultRes.status === 302) {
|
|
16
|
+
setResponseHeaders(event, defaultRes.headers);
|
|
17
|
+
setResponseStatus(event, defaultRes.status, defaultRes.statusText);
|
|
18
|
+
return send(event, JSON.stringify(defaultRes.body, null, 2));
|
|
19
|
+
}
|
|
20
|
+
if (import.meta.dev && typeof defaultRes.body !== "string" && Array.isArray(defaultRes.body.stack)) {
|
|
21
|
+
// normalize to string format expected by nuxt `error.vue`
|
|
22
|
+
defaultRes.body.stack = defaultRes.body.stack.join("\n");
|
|
23
|
+
}
|
|
24
|
+
const errorObject = defaultRes.body;
|
|
25
|
+
// remove proto/hostname/port from URL
|
|
26
|
+
const url = new URL(errorObject.url);
|
|
27
|
+
errorObject.url = withoutBase(url.pathname, useRuntimeConfig(event).app.baseURL) + url.search + url.hash;
|
|
28
|
+
// add default server message
|
|
29
|
+
errorObject.message ||= "Server Error";
|
|
30
|
+
// we will be rendering this error internally so we can pass along the error.data safely
|
|
31
|
+
errorObject.data ||= error.data;
|
|
32
|
+
errorObject.statusText ||= error.statusText || error.statusMessage;
|
|
33
|
+
delete defaultRes.headers["content-type"];
|
|
34
|
+
delete defaultRes.headers["content-security-policy"];
|
|
35
|
+
setResponseHeaders(event, defaultRes.headers);
|
|
36
|
+
// Access request headers
|
|
37
|
+
const reqHeaders = getRequestHeaders(event);
|
|
38
|
+
// Detect to avoid recursion in SSR rendering of errors
|
|
39
|
+
const isRenderingError = event.path.startsWith("/__nuxt_error") || !!reqHeaders["x-nuxt-error"];
|
|
40
|
+
// HTML response (via SSR)
|
|
41
|
+
const res = isRenderingError ? null : await useNitroApp().localFetch(withQuery(joinURL(useRuntimeConfig(event).app.baseURL, "/__nuxt_error"), errorObject), {
|
|
42
|
+
headers: {
|
|
43
|
+
...reqHeaders,
|
|
44
|
+
"x-nuxt-error": "true"
|
|
45
|
+
},
|
|
46
|
+
redirect: "manual"
|
|
47
|
+
}).catch(() => null);
|
|
48
|
+
if (event.handled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Fallback to static rendered error page
|
|
52
|
+
if (!res) {
|
|
53
|
+
const { template } = await import("../templates/error-500");
|
|
54
|
+
if (import.meta.dev) {
|
|
55
|
+
// TODO: Support `message` in template
|
|
56
|
+
errorObject.description = errorObject.message;
|
|
57
|
+
}
|
|
58
|
+
setResponseHeader(event, "Content-Type", "text/html;charset=UTF-8");
|
|
59
|
+
return send(event, template(errorObject));
|
|
60
|
+
}
|
|
61
|
+
const html = await res.text();
|
|
62
|
+
for (const [header, value] of res.headers.entries()) {
|
|
63
|
+
if (header === "set-cookie") {
|
|
64
|
+
appendResponseHeader(event, header, value);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
setResponseHeader(event, header, value);
|
|
68
|
+
}
|
|
69
|
+
setResponseStatus(event, res.status && res.status !== 200 ? res.status : defaultRes.status, res.statusText || defaultRes.statusText);
|
|
70
|
+
if (import.meta.dev && !import.meta.test && typeof html === "string") {
|
|
71
|
+
const prettyResponse = await defaultHandler(error, event, { json: false });
|
|
72
|
+
if (typeof prettyResponse.body === "string") {
|
|
73
|
+
return send(event, html.replace("</body>", `${generateErrorOverlayHTML(prettyResponse.body, { startMinimized: 300 <= status && status < 500 })}</body>`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return send(event, html);
|
|
77
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useNitroApp } from "nitropack/runtime";
|
|
2
|
+
import { destr } from "destr";
|
|
3
|
+
import { defineEventHandler, getQuery, readBody, setResponseHeaders } from "h3";
|
|
4
|
+
import { resolveUnrefHeadInput } from "@unhead/vue";
|
|
5
|
+
import { getRequestDependencies } from "vue-bundle-renderer/runtime";
|
|
6
|
+
import { getQuery as getURLQuery } from "ufo";
|
|
7
|
+
import { islandCache, islandPropCache } from "../utils/cache.mjs";
|
|
8
|
+
import { createSSRContext } from "../utils/renderer/app.mjs";
|
|
9
|
+
import { getSSRRenderer } from "../utils/renderer/build-files.mjs";
|
|
10
|
+
import { renderInlineStyles } from "../utils/renderer/inline-styles.mjs";
|
|
11
|
+
import { getClientIslandResponse, getServerComponentHTML, getSlotIslandResponse } from "../utils/renderer/islands.mjs";
|
|
12
|
+
const ISLAND_SUFFIX_RE = /\.json(?:\?.*)?$/;
|
|
13
|
+
export default defineEventHandler(async (event) => {
|
|
14
|
+
const nitroApp = useNitroApp();
|
|
15
|
+
setResponseHeaders(event, {
|
|
16
|
+
"content-type": "application/json;charset=utf-8",
|
|
17
|
+
"x-powered-by": "Nuxt"
|
|
18
|
+
});
|
|
19
|
+
if (import.meta.prerender && event.path && await islandCache.hasItem(event.path)) {
|
|
20
|
+
return islandCache.getItem(event.path);
|
|
21
|
+
}
|
|
22
|
+
const islandContext = await getIslandContext(event);
|
|
23
|
+
const ssrContext = {
|
|
24
|
+
...createSSRContext(event),
|
|
25
|
+
islandContext,
|
|
26
|
+
noSSR: false,
|
|
27
|
+
url: islandContext.url
|
|
28
|
+
};
|
|
29
|
+
// Render app
|
|
30
|
+
const renderer = await getSSRRenderer();
|
|
31
|
+
const renderResult = await renderer.renderToString(ssrContext).catch(async (err) => {
|
|
32
|
+
await ssrContext.nuxt?.hooks.callHook("app:error", err);
|
|
33
|
+
throw err;
|
|
34
|
+
});
|
|
35
|
+
// Handle errors
|
|
36
|
+
if (ssrContext.payload?.error) {
|
|
37
|
+
throw ssrContext.payload.error;
|
|
38
|
+
}
|
|
39
|
+
const inlinedStyles = await renderInlineStyles(ssrContext.modules ?? []);
|
|
40
|
+
await ssrContext.nuxt?.hooks.callHook("app:rendered", {
|
|
41
|
+
ssrContext,
|
|
42
|
+
renderResult
|
|
43
|
+
});
|
|
44
|
+
if (inlinedStyles.length) {
|
|
45
|
+
ssrContext.head.push({ style: inlinedStyles });
|
|
46
|
+
}
|
|
47
|
+
if (import.meta.dev) {
|
|
48
|
+
const { styles } = getRequestDependencies(ssrContext, renderer.rendererContext);
|
|
49
|
+
const link = [];
|
|
50
|
+
for (const resource of Object.values(styles)) {
|
|
51
|
+
// Do not add links to resources that are inlined (vite v5+)
|
|
52
|
+
if ("inline" in getURLQuery(resource.file)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Add CSS links in <head> for CSS files
|
|
56
|
+
// - in dev mode when rendering an island and the file has scoped styles and is not a page
|
|
57
|
+
if (resource.file.includes("scoped") && !resource.file.includes("pages/")) {
|
|
58
|
+
link.push({
|
|
59
|
+
rel: "stylesheet",
|
|
60
|
+
href: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
61
|
+
crossorigin: ""
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (link.length) {
|
|
66
|
+
ssrContext.head.push({ link }, { mode: "server" });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const islandHead = {};
|
|
70
|
+
for (const entry of ssrContext.head.entries.values()) {
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
72
|
+
for (const [key, value] of Object.entries(resolveUnrefHeadInput(entry.input))) {
|
|
73
|
+
const currentValue = islandHead[key];
|
|
74
|
+
if (Array.isArray(currentValue)) {
|
|
75
|
+
currentValue.push(...value);
|
|
76
|
+
} else {
|
|
77
|
+
islandHead[key] = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const islandResponse = {
|
|
82
|
+
id: islandContext.id,
|
|
83
|
+
head: islandHead,
|
|
84
|
+
html: getServerComponentHTML(renderResult.html),
|
|
85
|
+
components: getClientIslandResponse(ssrContext),
|
|
86
|
+
slots: getSlotIslandResponse(ssrContext)
|
|
87
|
+
};
|
|
88
|
+
await nitroApp.hooks.callHook("render:island", islandResponse, {
|
|
89
|
+
event,
|
|
90
|
+
islandContext
|
|
91
|
+
});
|
|
92
|
+
if (import.meta.prerender) {
|
|
93
|
+
await islandCache.setItem(`/__nuxt_island/${islandContext.name}_${islandContext.id}.json`, islandResponse);
|
|
94
|
+
await islandPropCache.setItem(`/__nuxt_island/${islandContext.name}_${islandContext.id}.json`, event.path);
|
|
95
|
+
}
|
|
96
|
+
return islandResponse;
|
|
97
|
+
});
|
|
98
|
+
async function getIslandContext(event) {
|
|
99
|
+
// TODO: Strict validation for url
|
|
100
|
+
let url = event.path || "";
|
|
101
|
+
if (import.meta.prerender && event.path && await islandPropCache.hasItem(event.path)) {
|
|
102
|
+
// rehydrate props from cache so we can rerender island if cache does not have it any more
|
|
103
|
+
url = await islandPropCache.getItem(event.path);
|
|
104
|
+
}
|
|
105
|
+
const componentParts = url.substring("/__nuxt_island".length + 1).replace(ISLAND_SUFFIX_RE, "").split("_");
|
|
106
|
+
const hashId = componentParts.length > 1 ? componentParts.pop() : undefined;
|
|
107
|
+
const componentName = componentParts.join("_");
|
|
108
|
+
// TODO: Validate context
|
|
109
|
+
const context = event.method === "GET" ? getQuery(event) : await readBody(event);
|
|
110
|
+
const ctx = {
|
|
111
|
+
url: "/",
|
|
112
|
+
...context,
|
|
113
|
+
id: hashId,
|
|
114
|
+
name: componentName,
|
|
115
|
+
props: destr(context.props) || {},
|
|
116
|
+
slots: {},
|
|
117
|
+
components: {}
|
|
118
|
+
};
|
|
119
|
+
return ctx;
|
|
120
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { getPrefetchLinks, getPreloadLinks, getRequestDependencies, renderResourceHeaders } from "vue-bundle-renderer/runtime";
|
|
3
|
+
import { appendResponseHeader, createError, getQuery, getResponseStatus, getResponseStatusText, writeEarlyHints } from "h3";
|
|
4
|
+
import { getQuery as getURLQuery, joinURL } from "ufo";
|
|
5
|
+
import { propsToString, renderSSRHead } from "@unhead/vue/server";
|
|
6
|
+
import destr from "destr";
|
|
7
|
+
import { defineRenderHandler, getRouteRules, useNitroApp } from "nitropack/runtime";
|
|
8
|
+
import { getRenderer } from "../utils/renderer/build-files.mjs";
|
|
9
|
+
import { payloadCache } from "../utils/cache.mjs";
|
|
10
|
+
import { renderPayloadJsonScript, renderPayloadResponse, renderPayloadScript, splitPayload } from "../utils/renderer/payload.mjs";
|
|
11
|
+
import { createSSRContext, setSSRError } from "../utils/renderer/app.mjs";
|
|
12
|
+
import { renderInlineStyles } from "../utils/renderer/inline-styles.mjs";
|
|
13
|
+
import { replaceIslandTeleports } from "../utils/renderer/islands.mjs";
|
|
14
|
+
// @ts-expect-error virtual file
|
|
15
|
+
import { renderSSRHeadOptions } from "#internal/unhead.config.mjs";
|
|
16
|
+
// @ts-expect-error virtual file
|
|
17
|
+
import { NUXT_ASYNC_CONTEXT, NUXT_EARLY_HINTS, NUXT_INLINE_STYLES, NUXT_JSON_PAYLOADS, NUXT_NO_SCRIPTS, NUXT_PAYLOAD_EXTRACTION, NUXT_RUNTIME_PAYLOAD_EXTRACTION, PARSE_ERROR_DATA } from "#internal/nuxt/nitro-config.mjs";
|
|
18
|
+
// @ts-expect-error virtual file
|
|
19
|
+
import { appHead, appTeleportAttrs, appTeleportTag, componentIslands, appManifest as isAppManifestEnabled } from "#internal/nuxt.config.mjs";
|
|
20
|
+
// @ts-expect-error virtual file
|
|
21
|
+
import entryIds from "#internal/nuxt/entry-ids.mjs";
|
|
22
|
+
// @ts-expect-error virtual file
|
|
23
|
+
import { entryFileName } from "#internal/entry-chunk.mjs";
|
|
24
|
+
// @ts-expect-error virtual file
|
|
25
|
+
import { buildAssetsURL, publicAssetsURL } from "#internal/nuxt/paths";
|
|
26
|
+
import { relative } from "pathe";
|
|
27
|
+
// @ts-expect-error private property consumed by vite-generated url helpers
|
|
28
|
+
globalThis.__buildAssetsURL = buildAssetsURL;
|
|
29
|
+
// @ts-expect-error private property consumed by vite-generated url helpers
|
|
30
|
+
globalThis.__publicAssetsURL = publicAssetsURL;
|
|
31
|
+
// Polyfill for unctx (https://github.com/unjs/unctx#native-async-context)
|
|
32
|
+
if (NUXT_ASYNC_CONTEXT && !("AsyncLocalStorage" in globalThis)) {
|
|
33
|
+
globalThis.AsyncLocalStorage = AsyncLocalStorage;
|
|
34
|
+
}
|
|
35
|
+
const HAS_APP_TELEPORTS = !!(appTeleportTag && appTeleportAttrs.id);
|
|
36
|
+
const APP_TELEPORT_OPEN_TAG = HAS_APP_TELEPORTS ? `<${appTeleportTag}${propsToString(appTeleportAttrs)}>` : "";
|
|
37
|
+
const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : "";
|
|
38
|
+
const PAYLOAD_URL_RE = NUXT_JSON_PAYLOADS ? /^[^?]*\/_payload.json(?:\?.*)?$/ : /^[^?]*\/_payload.js(?:\?.*)?$/;
|
|
39
|
+
const PAYLOAD_FILENAME = NUXT_JSON_PAYLOADS ? "_payload.json" : "_payload.js";
|
|
40
|
+
let entryPath;
|
|
41
|
+
export default defineRenderHandler(async (event) => {
|
|
42
|
+
const nitroApp = useNitroApp();
|
|
43
|
+
// Whether we're rendering an error page
|
|
44
|
+
const ssrError = event.path.startsWith("/__nuxt_error") ? getQuery(event) : null;
|
|
45
|
+
if (ssrError && !("__unenv__" in event.node.req)) {
|
|
46
|
+
throw createError({
|
|
47
|
+
status: 404,
|
|
48
|
+
statusText: "Page Not Found: /__nuxt_error",
|
|
49
|
+
message: "Page Not Found: /__nuxt_error"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Initialize ssr context
|
|
53
|
+
const ssrContext = createSSRContext(event);
|
|
54
|
+
// needed for hash hydration plugin to work
|
|
55
|
+
const headEntryOptions = { mode: "server" };
|
|
56
|
+
ssrContext.head.push(appHead, headEntryOptions);
|
|
57
|
+
if (ssrError) {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
59
|
+
const status = ssrError.status || ssrError.statusCode;
|
|
60
|
+
if (status) {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
62
|
+
ssrError.status = ssrError.statusCode = Number.parseInt(status);
|
|
63
|
+
}
|
|
64
|
+
if (PARSE_ERROR_DATA && typeof ssrError.data === "string") {
|
|
65
|
+
try {
|
|
66
|
+
ssrError.data = destr(ssrError.data);
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
setSSRError(ssrContext, ssrError);
|
|
70
|
+
}
|
|
71
|
+
// Get route options (for `ssr: false`, `isr`, `cache` and `noScripts`)
|
|
72
|
+
const routeOptions = getRouteRules(event);
|
|
73
|
+
// Whether we are prerendering route or using ISR/SWR caching
|
|
74
|
+
const _PAYLOAD_EXTRACTION = !ssrContext.noSSR && (import.meta.prerender && NUXT_PAYLOAD_EXTRACTION || NUXT_RUNTIME_PAYLOAD_EXTRACTION && (routeOptions.isr || routeOptions.cache));
|
|
75
|
+
const isRenderingPayload = (_PAYLOAD_EXTRACTION || import.meta.dev && routeOptions.prerender) && PAYLOAD_URL_RE.test(ssrContext.url);
|
|
76
|
+
if (isRenderingPayload) {
|
|
77
|
+
const url = ssrContext.url.substring(0, ssrContext.url.lastIndexOf("/")) || "/";
|
|
78
|
+
ssrContext.url = url;
|
|
79
|
+
event._path = event.node.req.url = url;
|
|
80
|
+
if (import.meta.prerender && await payloadCache.hasItem(url)) {
|
|
81
|
+
return payloadCache.getItem(url);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (routeOptions.ssr === false) {
|
|
85
|
+
ssrContext.noSSR = true;
|
|
86
|
+
}
|
|
87
|
+
const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(ssrContext.runtimeConfig.app.cdnURL || ssrContext.runtimeConfig.app.baseURL, ssrContext.url.replace(/\?.*$/, ""), PAYLOAD_FILENAME) + "?" + ssrContext.runtimeConfig.app.buildId : undefined;
|
|
88
|
+
// Render app
|
|
89
|
+
const renderer = await getRenderer(ssrContext);
|
|
90
|
+
// Render 103 Early Hints
|
|
91
|
+
if (NUXT_EARLY_HINTS && !isRenderingPayload && !import.meta.prerender) {
|
|
92
|
+
const { link } = renderResourceHeaders({}, renderer.rendererContext);
|
|
93
|
+
if (link) {
|
|
94
|
+
writeEarlyHints(event, link);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (NUXT_INLINE_STYLES) {
|
|
98
|
+
for (const id of entryIds) {
|
|
99
|
+
ssrContext.modules.add(id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const _rendered = await renderer.renderToString(ssrContext).catch(async (error) => {
|
|
103
|
+
// We use error to bypass full render if we have an early response we can make
|
|
104
|
+
// TODO: remove _renderResponse in nuxt v5
|
|
105
|
+
if ((ssrContext["~renderResponse"] || ssrContext._renderResponse) && error.message === "skipping render") {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
// Use explicitly thrown error in preference to subsequent rendering errors
|
|
109
|
+
const _err = !ssrError && ssrContext.payload?.error || error;
|
|
110
|
+
await ssrContext.nuxt?.hooks.callHook("app:error", _err);
|
|
111
|
+
throw _err;
|
|
112
|
+
});
|
|
113
|
+
// Render inline styles
|
|
114
|
+
// TODO: remove _renderResponse in nuxt v5
|
|
115
|
+
const inlinedStyles = NUXT_INLINE_STYLES && !ssrContext["~renderResponse"] && !ssrContext._renderResponse && !isRenderingPayload ? await renderInlineStyles(ssrContext.modules ?? []) : [];
|
|
116
|
+
await ssrContext.nuxt?.hooks.callHook("app:rendered", {
|
|
117
|
+
ssrContext,
|
|
118
|
+
renderResult: _rendered
|
|
119
|
+
});
|
|
120
|
+
if (ssrContext["~renderResponse"] || ssrContext._renderResponse) {
|
|
121
|
+
// TODO: remove _renderResponse in nuxt v5
|
|
122
|
+
return ssrContext["~renderResponse"] || ssrContext._renderResponse;
|
|
123
|
+
}
|
|
124
|
+
// Handle errors
|
|
125
|
+
if (ssrContext.payload?.error && !ssrError) {
|
|
126
|
+
throw ssrContext.payload.error;
|
|
127
|
+
}
|
|
128
|
+
// Directly render payload routes
|
|
129
|
+
if (isRenderingPayload) {
|
|
130
|
+
const response = renderPayloadResponse(ssrContext);
|
|
131
|
+
if (import.meta.prerender) {
|
|
132
|
+
await payloadCache.setItem(ssrContext.url, response);
|
|
133
|
+
}
|
|
134
|
+
return response;
|
|
135
|
+
}
|
|
136
|
+
if (_PAYLOAD_EXTRACTION && import.meta.prerender) {
|
|
137
|
+
// Hint nitro to prerender payload for this route
|
|
138
|
+
appendResponseHeader(event, "x-nitro-prerender", joinURL(ssrContext.url.replace(/\?.*$/, ""), PAYLOAD_FILENAME));
|
|
139
|
+
// Use same ssr context to generate payload for this route
|
|
140
|
+
await payloadCache.setItem(ssrContext.url === "/" ? "/" : ssrContext.url.replace(/\/$/, ""), renderPayloadResponse(ssrContext));
|
|
141
|
+
}
|
|
142
|
+
const NO_SCRIPTS = NUXT_NO_SCRIPTS || routeOptions.noScripts;
|
|
143
|
+
// Setup head
|
|
144
|
+
const { styles, scripts } = getRequestDependencies(ssrContext, renderer.rendererContext);
|
|
145
|
+
// 0. Add import map for stable chunk hashes
|
|
146
|
+
if (entryFileName && !NO_SCRIPTS) {
|
|
147
|
+
let path = entryPath;
|
|
148
|
+
if (!path) {
|
|
149
|
+
path = buildAssetsURL(entryFileName);
|
|
150
|
+
if (ssrContext.runtimeConfig.app.cdnURL || /^(?:\/|\.+\/)/.test(path)) {
|
|
151
|
+
// cache absolute entry path
|
|
152
|
+
entryPath = path;
|
|
153
|
+
} else {
|
|
154
|
+
// TODO: provide support for relative paths in assets as well
|
|
155
|
+
// relativise path
|
|
156
|
+
path = relative(event.path.replace(/\/[^/]+$/, "/"), joinURL("/", path));
|
|
157
|
+
if (!/^(?:\/|\.+\/)/.test(path)) {
|
|
158
|
+
path = `./${path}`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
ssrContext.head.push({ script: [{
|
|
163
|
+
tagPosition: "head",
|
|
164
|
+
tagPriority: -2,
|
|
165
|
+
type: "importmap",
|
|
166
|
+
innerHTML: JSON.stringify({ imports: { "#entry": path } })
|
|
167
|
+
}] }, headEntryOptions);
|
|
168
|
+
}
|
|
169
|
+
// 1. Preload payloads and app manifest
|
|
170
|
+
if (_PAYLOAD_EXTRACTION && !NO_SCRIPTS) {
|
|
171
|
+
ssrContext.head.push({ link: [NUXT_JSON_PAYLOADS ? {
|
|
172
|
+
rel: "preload",
|
|
173
|
+
as: "fetch",
|
|
174
|
+
crossorigin: "anonymous",
|
|
175
|
+
href: payloadURL
|
|
176
|
+
} : {
|
|
177
|
+
rel: "modulepreload",
|
|
178
|
+
crossorigin: "",
|
|
179
|
+
href: payloadURL
|
|
180
|
+
}] }, headEntryOptions);
|
|
181
|
+
}
|
|
182
|
+
if (isAppManifestEnabled && ssrContext["~preloadManifest"] && !NO_SCRIPTS) {
|
|
183
|
+
ssrContext.head.push({ link: [{
|
|
184
|
+
rel: "preload",
|
|
185
|
+
as: "fetch",
|
|
186
|
+
fetchpriority: "low",
|
|
187
|
+
crossorigin: "anonymous",
|
|
188
|
+
href: buildAssetsURL(`builds/meta/${ssrContext.runtimeConfig.app.buildId}.json`)
|
|
189
|
+
}] }, {
|
|
190
|
+
...headEntryOptions,
|
|
191
|
+
tagPriority: "low"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// 2. Styles
|
|
195
|
+
if (inlinedStyles.length) {
|
|
196
|
+
ssrContext.head.push({ style: inlinedStyles });
|
|
197
|
+
}
|
|
198
|
+
const link = [];
|
|
199
|
+
for (const resource of Object.values(styles)) {
|
|
200
|
+
// Do not add links to resources that are inlined (vite v5+)
|
|
201
|
+
if (import.meta.dev && "inline" in getURLQuery(resource.file)) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
// Add CSS links in <head> for CSS files
|
|
205
|
+
// - in production
|
|
206
|
+
// - in dev mode when not rendering an island
|
|
207
|
+
link.push({
|
|
208
|
+
rel: "stylesheet",
|
|
209
|
+
href: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
210
|
+
crossorigin: ""
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (link.length) {
|
|
214
|
+
ssrContext.head.push({ link }, headEntryOptions);
|
|
215
|
+
}
|
|
216
|
+
if (!NO_SCRIPTS) {
|
|
217
|
+
// 4. Resource Hints
|
|
218
|
+
ssrContext.head.push({ link: getPreloadLinks(ssrContext, renderer.rendererContext) }, headEntryOptions);
|
|
219
|
+
ssrContext.head.push({ link: getPrefetchLinks(ssrContext, renderer.rendererContext) }, headEntryOptions);
|
|
220
|
+
// 5. Payloads
|
|
221
|
+
ssrContext.head.push({ script: _PAYLOAD_EXTRACTION ? NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
|
|
222
|
+
ssrContext,
|
|
223
|
+
data: splitPayload(ssrContext).initial,
|
|
224
|
+
src: payloadURL
|
|
225
|
+
}) : renderPayloadScript({
|
|
226
|
+
ssrContext,
|
|
227
|
+
data: splitPayload(ssrContext).initial,
|
|
228
|
+
routeOptions,
|
|
229
|
+
src: payloadURL
|
|
230
|
+
}) : NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
|
|
231
|
+
ssrContext,
|
|
232
|
+
data: ssrContext.payload
|
|
233
|
+
}) : renderPayloadScript({
|
|
234
|
+
ssrContext,
|
|
235
|
+
data: ssrContext.payload,
|
|
236
|
+
routeOptions
|
|
237
|
+
}) }, {
|
|
238
|
+
...headEntryOptions,
|
|
239
|
+
tagPosition: "bodyClose",
|
|
240
|
+
tagPriority: "high"
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// 6. Scripts
|
|
244
|
+
if (!routeOptions.noScripts) {
|
|
245
|
+
const tagPosition = _PAYLOAD_EXTRACTION && !NUXT_JSON_PAYLOADS ? "bodyClose" : "head";
|
|
246
|
+
ssrContext.head.push({ script: Object.values(scripts).map((resource) => ({
|
|
247
|
+
type: resource.module ? "module" : null,
|
|
248
|
+
src: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
249
|
+
defer: resource.module ? null : true,
|
|
250
|
+
tagPosition,
|
|
251
|
+
crossorigin: ""
|
|
252
|
+
})) }, headEntryOptions);
|
|
253
|
+
}
|
|
254
|
+
const { headTags, bodyTags, bodyTagsOpen, htmlAttrs, bodyAttrs } = await renderSSRHead(ssrContext.head, renderSSRHeadOptions);
|
|
255
|
+
// Create render context
|
|
256
|
+
const htmlContext = {
|
|
257
|
+
htmlAttrs: htmlAttrs ? [htmlAttrs] : [],
|
|
258
|
+
head: normalizeChunks([headTags]),
|
|
259
|
+
bodyAttrs: bodyAttrs ? [bodyAttrs] : [],
|
|
260
|
+
bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]),
|
|
261
|
+
body: [componentIslands ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html, APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportAttrs.id}`]]) : "") + APP_TELEPORT_CLOSE_TAG],
|
|
262
|
+
bodyAppend: [bodyTags]
|
|
263
|
+
};
|
|
264
|
+
// Allow hooking into the rendered result
|
|
265
|
+
await nitroApp.hooks.callHook("render:html", htmlContext, { event });
|
|
266
|
+
// Construct HTML response
|
|
267
|
+
return {
|
|
268
|
+
body: renderHTMLDocument(htmlContext),
|
|
269
|
+
statusCode: getResponseStatus(event),
|
|
270
|
+
statusMessage: getResponseStatusText(event),
|
|
271
|
+
headers: {
|
|
272
|
+
"content-type": "text/html;charset=utf-8",
|
|
273
|
+
"x-powered-by": "Nuxt"
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
function normalizeChunks(chunks) {
|
|
278
|
+
const result = [];
|
|
279
|
+
for (const _chunk of chunks) {
|
|
280
|
+
const chunk = _chunk?.trim();
|
|
281
|
+
if (chunk) {
|
|
282
|
+
result.push(chunk);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
function joinTags(tags) {
|
|
288
|
+
return tags.join("");
|
|
289
|
+
}
|
|
290
|
+
function joinAttrs(chunks) {
|
|
291
|
+
if (chunks.length === 0) {
|
|
292
|
+
return "";
|
|
293
|
+
}
|
|
294
|
+
return " " + chunks.join(" ");
|
|
295
|
+
}
|
|
296
|
+
function renderHTMLDocument(html) {
|
|
297
|
+
return "<!DOCTYPE html>" + `<html${joinAttrs(html.htmlAttrs)}>` + `<head>${joinTags(html.head)}</head>` + `<body${joinAttrs(html.bodyAttrs)}>${joinTags(html.bodyPrepend)}${joinTags(html.body)}${joinTags(html.bodyAppend)}</body>` + "</html>";
|
|
298
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { consola } from "consola";
|
|
3
|
+
import { stringify } from "devalue";
|
|
4
|
+
import { withTrailingSlash } from "ufo";
|
|
5
|
+
import { getContext } from "unctx";
|
|
6
|
+
import { captureRawStackTrace, parseRawStackTrace } from "errx";
|
|
7
|
+
import { isVNode } from "vue";
|
|
8
|
+
// @ts-expect-error virtual file
|
|
9
|
+
import { rootDir } from "#internal/dev-server-logs-options";
|
|
10
|
+
// @ts-expect-error virtual file
|
|
11
|
+
import { appId } from "#internal/nuxt.config.mjs";
|
|
12
|
+
const devReducers = {
|
|
13
|
+
VNode: (data) => isVNode(data) ? {
|
|
14
|
+
type: data.type,
|
|
15
|
+
props: data.props
|
|
16
|
+
} : undefined,
|
|
17
|
+
URL: (data) => data instanceof URL ? data.toString() : undefined
|
|
18
|
+
};
|
|
19
|
+
const asyncContext = getContext("nuxt-dev", {
|
|
20
|
+
asyncContext: true,
|
|
21
|
+
AsyncLocalStorage
|
|
22
|
+
});
|
|
23
|
+
export default (nitroApp) => {
|
|
24
|
+
const handler = nitroApp.h3App.handler;
|
|
25
|
+
nitroApp.h3App.handler = (event) => {
|
|
26
|
+
return asyncContext.callAsync({
|
|
27
|
+
logs: [],
|
|
28
|
+
event
|
|
29
|
+
}, () => handler(event));
|
|
30
|
+
};
|
|
31
|
+
onConsoleLog((_log) => {
|
|
32
|
+
const ctx = asyncContext.tryUse();
|
|
33
|
+
if (!ctx) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const rawStack = captureRawStackTrace();
|
|
37
|
+
if (!rawStack || rawStack.includes("runtime/vite-node.mjs")) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const trace = [];
|
|
41
|
+
let filename = "";
|
|
42
|
+
for (const entry of parseRawStackTrace(rawStack)) {
|
|
43
|
+
if (entry.source === import.meta.url) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (EXCLUDE_TRACE_RE.test(entry.source)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
filename ||= entry.source.replace(withTrailingSlash(rootDir), "");
|
|
50
|
+
trace.push({
|
|
51
|
+
...entry,
|
|
52
|
+
source: entry.source.startsWith("file://") ? entry.source.replace("file://", "") : entry.source
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const log = {
|
|
56
|
+
..._log,
|
|
57
|
+
filename,
|
|
58
|
+
stack: trace
|
|
59
|
+
};
|
|
60
|
+
// retain log to be include in the next render
|
|
61
|
+
ctx.logs.push(log);
|
|
62
|
+
});
|
|
63
|
+
nitroApp.hooks.hook("afterResponse", () => {
|
|
64
|
+
const ctx = asyncContext.tryUse();
|
|
65
|
+
if (!ctx) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
return nitroApp.hooks.callHook("dev:ssr-logs", {
|
|
69
|
+
logs: ctx.logs,
|
|
70
|
+
path: ctx.event.path
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
// Pass any logs to the client
|
|
74
|
+
nitroApp.hooks.hook("render:html", (htmlContext) => {
|
|
75
|
+
const ctx = asyncContext.tryUse();
|
|
76
|
+
if (!ctx) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const reducers = Object.assign(Object.create(null), devReducers, ctx.event.context["~payloadReducers"]);
|
|
81
|
+
htmlContext.bodyAppend.unshift(`<script type="application/json" data-nuxt-logs="${appId}">${stringify(ctx.logs, reducers)}<\/script>`);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
const shortError = e instanceof Error && "toString" in e ? ` Received \`${e.toString()}\`.` : "";
|
|
84
|
+
console.warn(`[nuxt] Failed to stringify dev server logs.${shortError} You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/4.x/api/composables/use-nuxt-app#payload.`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const EXCLUDE_TRACE_RE = /\/node_modules\/(?:.*\/)?(?:nuxt|nuxt-nightly|nuxt-edge|nuxt3|consola|@vue)\/|core\/runtime\/nitro/;
|
|
89
|
+
function onConsoleLog(callback) {
|
|
90
|
+
consola.addReporter({ log(logObj) {
|
|
91
|
+
callback(logObj);
|
|
92
|
+
} });
|
|
93
|
+
consola.wrapConsole();
|
|
94
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { escapeHtml } from "@vue/shared";
|
|
2
|
+
const _messages = {
|
|
3
|
+
"appName": "Nuxt",
|
|
4
|
+
"status": 500,
|
|
5
|
+
"statusText": "Internal server error",
|
|
6
|
+
"description": "This page is temporarily unavailable.",
|
|
7
|
+
"refresh": "Refresh this page"
|
|
8
|
+
};
|
|
9
|
+
export const template = (messages) => {
|
|
10
|
+
messages = {
|
|
11
|
+
..._messages,
|
|
12
|
+
...messages
|
|
13
|
+
};
|
|
14
|
+
return "<!DOCTYPE html><html lang=\"en\"><head><title>" + escapeHtml(messages.status) + " - " + escapeHtml(messages.statusText) + " | " + escapeHtml(messages.appName) + "</title><meta charset=\"utf-8\"><meta content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0\" name=\"viewport\"><script>!function(){const e=document.createElement(\"link\").relList;if(!(e&&e.supports&&e.supports(\"modulepreload\"))){for(const e of document.querySelectorAll('link[rel=\"modulepreload\"]'))r(e);new MutationObserver(e=>{for(const o of e)if(\"childList\"===o.type)for(const e of o.addedNodes)\"LINK\"===e.tagName&&\"modulepreload\"===e.rel&&r(e)}).observe(document,{childList:!0,subtree:!0})}function r(e){if(e.ep)return;e.ep=!0;const r=function(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerPolicy&&(r.referrerPolicy=e.referrerPolicy),\"use-credentials\"===e.crossOrigin?r.credentials=\"include\":\"anonymous\"===e.crossOrigin?r.credentials=\"omit\":r.credentials=\"same-origin\",r}(e);fetch(e.href,r)}}();<\/script><style>*,:after,:before{border-color:var(--un-default-border-color,#e5e7eb);border-style:solid;border-width:0;box-sizing:border-box}:after,:before{--un-content:\"\"}html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}h1,h2{font-size:inherit;font-weight:inherit}h1,h2,p{margin:0}*,:after,:before{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 transparent;--un-ring-shadow:0 0 transparent;--un-shadow-inset: ;--un-shadow:0 0 transparent;--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }.grid{display:grid}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.max-w-520px{max-width:520px}.min-h-screen{min-height:100vh}.place-content-center{place-content:center}.overflow-hidden{overflow:hidden}.bg-white{--un-bg-opacity:1;background-color:rgb(255 255 255/var(--un-bg-opacity))}.px-2{padding-left:.5rem;padding-right:.5rem}.text-center{text-align:center}.text-\\[80px\\]{font-size:80px}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\\[\\#020420\\]{--un-text-opacity:1;color:rgb(2 4 32/var(--un-text-opacity))}.text-\\[\\#64748B\\]{--un-text-opacity:1;color:rgb(100 116 139/var(--un-text-opacity))}.font-semibold{font-weight:600}.leading-none{line-height:1}.tracking-wide{letter-spacing:.025em}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.tabular-nums{--un-numeric-spacing:tabular-nums;font-variant-numeric:var(--un-ordinal) var(--un-slashed-zero) var(--un-numeric-figure) var(--un-numeric-spacing) var(--un-numeric-fraction)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media(prefers-color-scheme:dark){.dark\\:bg-\\[\\#020420\\]{--un-bg-opacity:1;background-color:rgb(2 4 32/var(--un-bg-opacity))}.dark\\:text-white{--un-text-opacity:1;color:rgb(255 255 255/var(--un-text-opacity))}}@media(min-width:640px){.sm\\:text-\\[110px\\]{font-size:110px}.sm\\:text-3xl{font-size:1.875rem;line-height:2.25rem}}</style></head><body class=\"antialiased bg-white dark:bg-[#020420] dark:text-white font-sans grid min-h-screen overflow-hidden place-content-center text-[#020420] tracking-wide\"><div class=\"max-w-520px text-center\"><h1 class=\"font-semibold leading-none mb-4 sm:text-[110px] tabular-nums text-[80px]\">" + escapeHtml(messages.status) + "</h1><h2 class=\"font-semibold mb-2 sm:text-3xl text-2xl\">" + escapeHtml(messages.statusText) + "</h2><p class=\"mb-4 px-2 text-[#64748B] text-md\">" + escapeHtml(messages.description) + "</p></div></body></html>";
|
|
15
|
+
};
|