@before.js/server
Version:
Enables data fetching with any React SSR app that uses React Router 5
106 lines (99 loc) • 2.93 kB
JSX
// @flow strict
import type {
ExtraTag,
DocumentInitialProps,
DocumentGetInitialProps,
Context,
Extractor,
DataType
} from 'Document.component';
import React, { Fragment } from 'react';
import { F, identity, path } from 'ramda';
import { Error } from './Error.component';
import serialize from 'serialize-javascript';
const getHeaderTags = (extractor: Extractor) => [
...extractor.getStyleElements(),
...extractor.getLinkElements()
];
const renderTags = ({ tag: Tag, content, name, attribs }: ExtraTag) => (
<Tag key={name} {...attribs} dangerouslySetInnerHTML={{ __html: content }} />
);
export function DocumentComponent({
helmet,
assets,
criticalCSS,
data,
error,
errorComponent: ErrorComponent,
filterServerData = identity,
extractor,
extraHeadTags = [],
extraBodyTags = []
}: DocumentInitialProps) {
// get attributes from React Helmet
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
const clientCss = path(['client', 'css'], assets);
return (
<html {...htmlAttrs}>
<head>
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"
/>
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
{extraHeadTags.map(renderTags)}
{helmet.style.toComponent()}
{helmet.script.toComponent()}
{extractor && getHeaderTags(extractor)}
{criticalCSS !== false && criticalCSS}
{clientCss && <link rel="stylesheet" href={clientCss} />}
</head>
<body {...bodyAttrs}>
{error ? (
ErrorComponent ? (
<ErrorComponent error={error} />
) : (
<Error message={error.message} stack={error.stack} />
)
) : (
<Fragment>
<Root />
<Data data={filterServerData(data)} />
</Fragment>
)}
{extractor && extractor.getScriptElements()}
{extraBodyTags.map(renderTags)}
</body>
</html>
);
}
DocumentComponent.getInitialProps = async ({
assets,
data,
renderPage,
generateCriticalCSS = F,
extractor,
...rest
}: Context): Promise<DocumentGetInitialProps> => {
const page = await renderPage(data);
const criticalCSS = generateCriticalCSS();
return { assets, criticalCSS, data, extractor, ...rest, ...page };
};
export const Root = () => <div id="root">BEFORE.JS-DATA</div>;
export const Data = ({ data }: { data: DataType }) => {
return (
<script
key={Math.random()}
id="server-app-state"
type="application/json"
dangerouslySetInnerHTML={{
__html: serialize(data, { isJSON: true }).replace(/<\/script>/g, '%3C/script%3E')
}}
/>
);
};