diff --git a/pages/api/outfitPageSSR.js b/pages/api/outfitPageSSR.js deleted file mode 100644 index 712c2f7..0000000 --- a/pages/api/outfitPageSSR.js +++ /dev/null @@ -1,151 +0,0 @@ -/** - * /api/outfitPageSSR also serves the initial request for /outfits/:id, to - * add title and meta tags. This primarily for sharing, like on Discord or - * Twitter or Facebook! - * - * The route is configured in vercel.json, at the project root. - * - * To be honest, we probably should have built Impress 2020 on Next.js, and - * then we'd be getting realistic server-side rendering across practically the - * whole app very cheaply. But this is a good hack for what we have! - * - * TODO: We could add the basic outfit page layout and image preview, to use - * SSR to decrease time-to-first-content for the end-user, too… - */ -const beeline = require("honeycomb-beeline")({ - writeKey: process.env["HONEYCOMB_WRITE_KEY"], - dataset: - process.env["NODE_ENV"] === "production" - ? "Dress to Impress (2020)" - : "Dress to Impress (2020, dev)", - serviceName: "impress-2020-gql-server", - disableInstrumentationOnLoad: true, -}); - -import escapeHtml from "escape-html"; -import fetch from "node-fetch"; - -import connectToDb from "../../src/server/db"; -import { normalizeRow } from "../../src/server/util"; - -async function handle(req, res) { - // Load index.html as our initial page content. If this fails, it probably - // means something is misconfigured in a big way; we don't have a great way - // to recover, and we'll just show an error message. - let initialHtml; - try { - initialHtml = await loadIndexPageHtml(); - } catch (e) { - console.error("Error loading index.html:", e); - return reject(res, "Sorry, there was an error loading this outfit page!"); - } - - // Load the given outfit by ID. If this fails, it's possible that it's just a - // problem with the SSR, and the client will be able to handle it better - // anyway, so just show the standard index.html and let the app load - // normally, as if there was no error. (We'll just log it.) - let outfit; - try { - outfit = await loadOutfitData(req.query.id); - } catch (e) { - console.error("Error loading outfit data:", e); - return sendHtml(res, initialHtml, 200); - } - - // Similarly, if the outfit isn't found, we just show index.html - but with a - // 404 and a gentler log message. - if (outfit == null) { - console.info(`Outfit not found: ${req.query.id}`); - return sendHtml(res, initialHtml, 404); - } - - const outfitName = outfit.name || "Untitled outfit"; - - // Okay, now let's rewrite the HTML to include some outfit data! - // - // WARNING!!! - // Be sure to always use `escapeHtml` when inserting user data!! - // WARNING!!! - // - let html = initialHtml; - - // Add the outfit name to the title. - html = html.replace( - /(.*)<\/title>/, - `<title>${escapeHtml(outfitName)} | Dress to Impress` - ); - - // Add sharing meta tags just before the tag. - const updatedAtTimestamp = Math.floor( - new Date(outfit.updatedAt).getTime() / 1000 - ); - const outfitUrl = - `https://impress-2020.openneo.net/outfits` + - `/${encodeURIComponent(outfit.id)}`; - const imageUrl = - `https://impress-outfit-images.openneo.net/outfits` + - `/${encodeURIComponent(outfit.id)}` + - `/v/${encodeURIComponent(updatedAtTimestamp)}` + - `/600.png`; - const metaTags = ` - - - - - - - `; - html = html.replace(/<\/head>/, `${metaTags}`); - - console.info(`Successfully SSR'd outfit ${outfit.id}`); - - return sendHtml(res, html); -} - -async function loadOutfitData(id) { - const db = await connectToDb(); - const [rows] = await db.query(`SELECT * FROM outfits WHERE id = ?;`, [id]); - if (rows.length === 0) { - return null; - } - - return normalizeRow(rows[0]); -} - -let cachedIndexPageHtml = null; -async function loadIndexPageHtml() { - if (cachedIndexPageHtml == null) { - // Request the same built copy of index.html that we're already serving at - // our homepage. - const homepageUrl = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}/` - : process.env.NODE_ENV === "development" - ? "http://localhost:3000/" - : "https://impress-2020.openneo.net/"; - const liveIndexPageHtml = await fetch(homepageUrl).then((res) => - res.text() - ); - cachedIndexPageHtml = liveIndexPageHtml; - } - - return cachedIndexPageHtml; -} - -function reject(res, message, status = 400) { - res.setHeader("Content-Type", "text/plain; charset=utf8"); - return res.status(status).send(message); -} - -function sendHtml(res, html, status = 200) { - res.setHeader("Content-Type", "text/html"); - return res.status(status).send(html); -} - -async function handleWithBeeline(req, res) { - beeline.withTrace( - { name: "api/outfitPageSSR", operation_name: "api/outfitPageSSR" }, - () => handle(req, res) - ); -} - -export default handleWithBeeline;