Superglue's generators does not include Server Side Rendering, but we can add support using Humid, a SSR library built for Superglue.
Follow the instructions. Then, if you're using esbuild, create a app/javascript/server_rendering.js:
import React from 'react';
import { Application } from '@thoughtbot/superglue';
import { buildVisitAndRemote } from './application_visit';
import { pageIdentifierToPageComponent } from './page_to_page_mapping';
import { store } from './store'
import { renderToString } from 'react-dom/server';
require("source-map-support").install({
retrieveSourceMap: filename => {
return {
url: filename,
map: readSourceMap(filename)
};
}
});
setHumidRenderer((json, baseUrl, path) => {
const initialState = JSON.parse(json)
return renderToString(
<Application
className="full-height"
// The base url prefixed to all calls made by the `visit`
// and `remote` thunks.
baseUrl={baseUrl}
// The global var SUPERGLUE_INITIAL_PAGE_STATE is set by your erb
// template, e.g., index.html.erb
initialPage={initialState}
// The initial path of the page, e.g., /foobar
path={path}
// Callback used to setup visit and remote
buildVisitAndRemote={buildVisitAndRemote}
// Callback used to setup the store
store={store}
// Mapping between the page identifier to page component
mapping={pageIdentifierToPageComponent}
/>,
{
concurrentFeatures: false,
}
)
})
!> Do not render spacing inside of <div id="app">. If you do, React will not hydrate properly and warn Hydration failed because the initial UI does not match what was rendered on the server
Change your application.js to use hydrateRoot:
- import { createRoot } from 'react-dom/client';
+ import { hydrateRoot } from 'react-dom/client';
and change the rest of application.js accordingly. For example:
import React from 'react';
import { Application, VisitResponse } from '@thoughtbot/superglue';
import { hydrateRoot } from 'react-dom/client';
import { buildVisitAndRemote } from './application_visit';
import { pageIdentifierToPageComponent } from './page_to_page_mapping';
import { store } from './store'
if (typeof window !== "undefined") {
document.addEventListener("DOMContentLoaded", function () {
const appEl = document.getElementById("app");
const location = window.location;
if (appEl) {
hydrateRoot(appEl,
<Application
className="full-height"
// The base url prefixed to all calls made by the `visit`
// and `remote` thunks.
baseUrl={location.origin}
// The global var SUPERGLUE_INITIAL_PAGE_STATE is set by your erb
// template, e.g., index.html.erb
initialPage={window.SUPERGLUE_INITIAL_PAGE_STATE}
// The initial path of the page, e.g., /foobar
path={location.pathname + location.search + location.hash}
// Callback used to setup visit and remote
buildVisitAndRemote={buildVisitAndRemote}
// Callback used to setup the store
store={store}
// Mapping between the page identifier to page component
mapping={pageIdentifierToPageComponent}
/>
);
}
});
}
and add build script your package.json to build both the client and server js bundles. For example:
"build": "yarn run build:web && yarn run build:ssr",
"build:web": "esbuild app/javascript/application.js --bundle --sourcemap --outdir=app/assets/builds --loader:.js=jsx --loader:.svg=dataurl --public-path=/assets",
"build:ssr": "node ./build-ssr.mjs",