import type { NextPageContext } from "next"; import type { DocumentContext, DocumentProps } from "next/document"; import Document, { Head, Html, Main, NextScript } from "next/document"; import { z } from "zod"; import { csp } from "@lib/csp"; type Props = Record & DocumentProps; function setHeader(ctx: NextPageContext, name: string, value: string) { try { ctx.res?.setHeader(name, value); } catch (e) { // Getting "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when revalidate calendar chache console.log(`Error setting header ${name}=${value} for ${ctx.asPath || "unknown asPath"}`, e); } } class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { const { nonce } = csp(ctx.req || null, ctx.res || null); if (!process.env.CSP_POLICY) { setHeader(ctx, "x-csp", "not-opted-in"); } else if (!ctx.res?.getHeader("x-csp")) { // If x-csp not set by gSSP, then it's initialPropsOnly setHeader(ctx, "x-csp", "initialPropsOnly"); } const asPath = ctx.asPath || ""; // Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only const parsedUrl = new URL(asPath, "https://dummyurl"); const isEmbed = parsedUrl.pathname.endsWith("/embed") || parsedUrl.searchParams.get("embedType") !== null; const embedColorScheme = parsedUrl.searchParams.get("ui.color-scheme"); const initialProps = await Document.getInitialProps(ctx); return { isEmbed, embedColorScheme, nonce, ...initialProps }; } render() { const { locale } = this.props.__NEXT_DATA__; const { isEmbed, embedColorScheme } = this.props; const nonceParsed = z.string().safeParse(this.props.nonce); const nonce = nonceParsed.success ? nonceParsed.data : ""; return ( {(process.env.NODE_ENV === "development" || process.env.VERCEL_ENV === "preview") && ( // eslint-disable-next-line @next/next/no-sync-scripts