Open nathanjhood opened 1 month ago
Great idea in here about SSR and splitting the HTML around a comment...
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"><!--not rendered--></div>
<script type="module" src="./src/ClientApp.jsx"></script>
</body>
</html>
// server.js
import express from 'express';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import renderApp from './dist/server/ServerApp.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PORT = process.env.PORT || 3001;
// Read the built HTML file
const html = fs.readFileSync(path.resolve(__dirname, './dist/client/index.html')).toString();
const [head, tail] = html.split('<!--not rendered-->');
const app = express();
// Serve static assets
app.use('/assets', express.static(path.resolve(__dirname, './dist/client/assets')));
// Handle all other routes with server-side rendering
app.use((req, res) => {
res.write(head);
const stream = renderApp(req.url, {
onShellReady() {
stream.pipe(res);
},
onShellError(err) {
console.error(err);
res.status(500).send('Internal Server Error');
},
onAllReady() {
res.write(tail);
res.end();
},
onError(err) {
console.error(err);
}
});
});
app.listen(PORT, () => {
console.log(`Listening on http://localhost:${PORT}`);
});
More SSR fun:
If you call ReactDOM.hydrate() on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
The text in bold is the main difference. render may change your node if there is a difference between the initial DOM and the current DOM. hydrate will only attach event handlers.
and:
ReactDOM.hydrate() is same as render(), but it is used to hydrate(attach event listeners) a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
Using ReactDOM.render() to hydrate a server-rendered container is deprecated because of slowness and will be removed in React 17 so use hydrate() instead.
Had some fun using
ReactDom.Server.renderToString(<App/>)
... accidently created an SSR mode foresbuild-scripts
React projects!The idea is simple:
string
(unicode I guess), and renders the string on the server; the string is re-rendered from scratch on every client request and sent as the server's response to those client requestsReactDom.Server.renderToString(<App/>)
renders components into strings, specifically to be used on the server; the stringified HTML contains all the React markup attributes of the fully built appReactDom.Server.renderToStaticMarkup(<App/>)
does the same thing, but without the React stuff added inThis executable file runs an http server on HOST and PORT (guess the defaults); the return result of it's
App()
function are passed torenderToString
, then formatted byprettier
with it's HTML parser, then returned as a server response 200 with content typetext/html
.I borrowed a sweet trick from Expo, by basically stuffing the entire
index.html
contents into a React component, which is what we're rendering here.Visiting HOST:PORT in the browser with the server running confirms that the app and some basic content/styles are rendering.
Logging anything, anywhere, inside the component with
console.log
prints to stdout on the terminal which is running the server; the console instance does not print to the browser (client-side).This is a huge goal for me that I thought might still be some time away. :+1: