Delete outfitPageSSR
Oh right, we implemented this with Next.js SSR in `/pages/outfits/[id].js`, so we don't need this anymore!
This commit is contained in:
parent
9753cbe173
commit
eaadfd09ef
1 changed files with 0 additions and 151 deletions
|
@ -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>/,
|
|
||||||
`<title>${escapeHtml(outfitName)} | Dress to Impress</title>`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add sharing meta tags just before the </head> 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 = `
|
|
||||||
<meta property="og:title" content="${escapeHtml(outfitName)}">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:image" content="${escapeHtml(imageUrl)}">
|
|
||||||
<meta property="og:url" content="${escapeHtml(outfitUrl)}">
|
|
||||||
<meta property="og:site_name" content="Dress to Impress">
|
|
||||||
<meta property="og:description" content="A custom Neopets outfit, designed on Dress to Impress!">
|
|
||||||
`;
|
|
||||||
html = html.replace(/<\/head>/, `${metaTags}</head>`);
|
|
||||||
|
|
||||||
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;
|
|
Loading…
Reference in a new issue