Created
October 15, 2020 20:00
-
-
Save jarkkosyrjala/cac1b827a52d210dac983745c4f21861 to your computer and use it in GitHub Desktop.
Render static content as HTML and get the content from server side rendered HTML back as props before hydration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react' | |
import DynamicAppWithStaticSections, { | |
DynamicAppWithStaticSectionsProps, | |
DynamicAppWithStaticSectionsHydrationProps, | |
} from './DynamicAppWithStaticSections' | |
import { hydrate } from 'react-dom' | |
interface AppWindow extends Window { | |
__APP__: DynamicAppWithStaticSectionsHydrationProps | |
} | |
declare const window: AppWindow | |
const partiallyStaticComponentProps: DynamicAppWithStaticSectionsProps = { | |
// Only part of the props are passed to JSON in DOM | |
...window.__APP__, | |
// Get the rendered html and pass it back as props | |
staticSections: Array.from( | |
document.querySelectorAll('.static-sections > section > div'), | |
(div) => div.innerHTML, | |
), | |
} | |
hydrate( | |
<DynamicAppWithStaticSections {...partiallyStaticComponentProps} />, | |
document.getElementById('app'), | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react' | |
import { renderToStaticMarkup } from 'react-dom/server' | |
import { useState } from 'react' | |
export interface DynamicAppWithStaticSectionsProps { | |
dynamicText: string | |
staticSections: string[] | |
} | |
export type DynamicAppWithStaticSectionsHydrationProps = Omit< | |
DynamicAppWithStaticSectionsProps, | |
'staticSections' | |
> | |
interface SectionWithStaticContentProps { | |
html: string | |
} | |
const SectionWithStaticContent: React.FC<SectionWithStaticContentProps> = ({ html }) => { | |
const [show, setShow] = useState<boolean>(true) | |
return ( | |
<section> | |
<button onClick={() => setShow(!show)}>Toggle</button> | |
<div | |
style={{ display: show ? 'block' : 'none' }} | |
dangerouslySetInnerHTML={{ __html: html }} | |
/> | |
} | |
</section> | |
) | |
} | |
const DynamicAppWithStaticSections: React.FC<DynamicAppWithStaticSectionsProps> = ({ | |
dynamicText, | |
staticSections, | |
}) => { | |
return ( | |
<> | |
<div>{renderToStaticMarkup(<>{dynamicText}</>)}</div> | |
<div className="static-sections"> | |
{staticSections.map((html) => ( | |
<SectionWithStaticContent html={html} /> | |
))} | |
</div> | |
</> | |
) | |
} | |
export default DynamicAppWithStaticSections |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react' | |
import { DynamicAppWithStaticSectionsProps } from './DynamicAppWithStaticSections' | |
export interface HtmlPageProps { | |
partiallyStaticComponent: DynamicAppWithStaticSectionsProps | |
} | |
/** | |
* Html template that prints the hydrations props as JSON | |
*/ | |
const Html: React.FC<HtmlPageProps> = ({ partiallyStaticComponent, children }) => { | |
let { staticSections: _omit, ...hydrationProps } = partiallyStaticComponent | |
return ( | |
<html> | |
<body> | |
<h1>Example</h1> | |
<div id="app">{children}</div> | |
<script | |
dangerouslySetInnerHTML={{ | |
__html: `window.__APP__ = ${JSON.stringify(hydrationProps)}`, | |
}} | |
/> | |
</body> | |
</html> | |
) | |
} | |
export default Html |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { NextFunction, Request, Response } from 'express-serve-static-core' | |
import * as React from 'react' | |
import DynamicAppWithStaticSections, { | |
DynamicAppWithStaticSectionsProps, | |
} from './DynamicAppWithStaticSections' | |
import { renderToStaticMarkup, renderToString } from 'react-dom/server' | |
import Html from './Html' | |
const getPartiallyStaticComponentProps = async (): Promise<DynamicAppWithStaticSectionsProps> => { | |
// Fetch from server etc... | |
return Promise.resolve({ | |
dynamicText: 'foo', | |
staticSections: ['Hello', 'World'], | |
}) | |
} | |
const exampleExpressRouteHandler = async ( | |
_req: Request, | |
res: Response, | |
_next: NextFunction, | |
): Promise<any> => { | |
// get the props from somewhere | |
const partiallyStaticComponentProps = await getPartiallyStaticComponentProps() | |
return res.send( | |
renderToString( | |
<Html partiallyStaticComponent={partiallyStaticComponentProps}> | |
<DynamicAppWithStaticSections | |
{...{ | |
...partiallyStaticComponentProps, | |
staticSections: partiallyStaticComponentProps.staticSections.map((content) => | |
renderToStaticMarkup(<div className="fancy-but-static-sub-component">{content}</div>), | |
), | |
}} | |
/> | |
</Html>, | |
), | |
) | |
} |
Of course when rendering purely static content then the component does not need to be hydrated at all. However, when ever there's anything interactive then hydration is needed and for hydration to work without re-render, the props need to match.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The way hydration works is that the props need to be embedded to page as JSON so that it can be hydrated (or in some cases the content is in separate json file that is being loaded before hydration).
In a content heavy page this kind of JSON string can basically double the weight of a HTML file being served. Basically the content is printed twice: first as HTML and then the raw data as JSON.
Instead of printing the content twice, wouldn't it make sense to reuse the server side rendered html for hydration and get it back from the element.innerHTML?