Skip to content

Instantly share code, notes, and snippets.

@peat-psuwit
Created April 23, 2024 17:46
Show Gist options
  • Save peat-psuwit/549c4c70473f9aab9214ec2b8c87a351 to your computer and use it in GitHub Desktop.
Save peat-psuwit/549c4c70473f9aab9214ec2b8c87a351 to your computer and use it in GitHub Desktop.
streamingHTML, v2 (now works with Firefox)
/*
* Simple HTML streaming concept, inspired by how NextJS do it, but without
* React.
*
* It works by initially sending a skeleton HTML to the browser, but,
* importantly, this skeleton is unclosed, which allows server to stream in
* more content. However, you cannot go back to edit the HTML content you've
* already sent. Or can you???
*
* Turns out, if the additional content is a <script> tag which changes the
* innerHTML of an existing node, then you _can_ change anything you've already
* sent. This is essentially now Next.js can send stream additional stuff to the
* page under the same connection without additional requests, however RSC
* (React Server Component) payload is used instead of raw HTML.
*
* Have fun!
*
* Author: Ratchanan Srirattanamet
* SPDX-License-Identifier: CC0-1.0
*/
import { setTimeout } from "node:timers/promises";
import express from "express";
async function getContent() {
// Simulates querying DB, doing formatting etc. (Exacerbated).
await setTimeout(5000);
return {
id: "main-content",
innerHTML: "<h1>Hello, world!</h1>",
};
}
const app = express();
app.get("/", async (req, res) => {
res.status(200);
// Without this, browsers might not be able to infer from the partial response
// that this is an HTML and might not start rendering.
res.header('Content-Type', 'text/html; charset=utf-8');
// The skeleton HTML.
// TODO: maybe de-indent.
res.write(`
<!DOCTYPE html>
<html>
<head>
<title>Hello, world</title>
</head>
<body>
<div id="main-content">Loading...</div>
`);
// Now let's generate the actual content.
let content = await getContent();
// And then send the JS to instruct the browser to plug the content into the
// skeleton.
res.write(`
<script>
document.getElementById(${JSON.stringify(content.id)}).innerHTML = ${JSON.stringify(content.innerHTML)};
</script>
`);
// Now that everything is sent to the client. Be courteous and send the
// closing tags to the client. This is probably not strictly needed, but why
// not?
res.write(`
</body>
</html>
`);
// And finally, closes the request-response cycle.
res.end();
});
console.log("http://localhost:3000");
app.listen(3000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment