/**
* Minimal OpenGraph-enriched HTML landing page for the `/` of a Worker.
*
* Meta's WhatsApp crawler renders a card preview for URL buttons in
* templates. Without OG tags on the button's target, the preview shows the
* bare `*.workers.dev` domain — bad look. This helper returns a tiny HTML
* page with OG + Twitter meta tags + an optional human redirect. Consumers
* wire it at `app.get('/', c => new Response(landingHtml({...}), {headers}))`.
*
* The `redirectTo` value drives a `` — humans
* bounce to your real site after `redirectDelaySeconds`; the WhatsApp
* crawler stops at the first HTML response and reads OG tags there.
*/
export interface LandingHtmlOptions {
/** OG title + `
`. Required. */
title: string;
/** OG description + Twitter description + ``. Required. */
description: string;
/** `og:url` — canonical URL of this page. Required. */
url: string;
/** Where a human viewer should end up. When set, adds a meta-refresh redirect. */
redirectTo?: string;
/** Redirect delay in seconds. Default 2. Ignored when `redirectTo` is empty. */
redirectDelaySeconds?: number;
/** `og:site_name`. Default = title. */
siteName?: string;
/** `og:locale`. Default `pt_BR`. */
locale?: string;
/** BCP-47 language for ``. Default `pt-BR`. */
lang?: string;
/** `og:image` URL. Optional. */
image?: string;
/** Emoji or short glyph shown above the title in the body. Default `⚡`. */
glyph?: string;
/**
* `Cache-Control` value returned in headers when consumers use the
* `landingResponse` helper. Default `public, max-age=3600` so Meta's
* crawler doesn't refetch every send.
*/
cacheControl?: string;
}
/**
* Render the HTML string. `cacheControl` is ignored here — use it via
* `landingResponse` when constructing the `Response`.
*/
export function landingHtml(opts: LandingHtmlOptions): string {
const {
title,
description,
url,
redirectTo,
redirectDelaySeconds = 2,
siteName = title,
locale = 'pt_BR',
lang = 'pt-BR',
image,
glyph = '⚡',
} = opts;
const esc = escapeHtml;
const refreshTag = redirectTo
? ``
: '';
const imageTag = image ? `` : '';
const redirectLine = redirectTo
? `Redirecionando para ${esc(redirectTo.replace(/^https?:\/\//, ''))}…
`
: '';
return `
${esc(title)}
${imageTag}
${refreshTag}
${esc(glyph)} ${esc(title)}
${esc(description)}
${redirectLine}
`;
}
/**
* Convenience — build a full `Response` with sensible headers.
*
* app.get('/', () => landingResponse({ title: 'Zap Prime', description: '...', url: env.PUBLIC_BASE_URL, redirectTo: 'https://propmind.co' }));
*/
export function landingResponse(opts: LandingHtmlOptions): Response {
return new Response(landingHtml(opts), {
headers: {
'content-type': 'text/html; charset=utf-8',
'cache-control': opts.cacheControl ?? 'public, max-age=3600',
},
});
}
function escapeHtml(s: string): string {
return String(s)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}