Build simple website with deno + react + react-router-dom and support server side rendering. no webpack or babel.
lets the code:
Make dependencies for server ./deps-server.ts
this is dependencies for server app.
export { Dero } from "https://deno.land/x/dero@0.2.7/mod.ts";
export type { HttpRequest, HttpResponse, NextFunction } from "https://deno.land/x/dero@0.2.7/mod.ts";
export { default as ReactDOMServer } from "https://dev.jspm.io/react-dom@17.0.2/server";
Make dependencies for client ./deps-client.ts
this is dependencies for client side such as react.
// maybe you can put @deno-types. example use anywhere :)
import ReactClient from "https://dev.jspm.io/react@17.0.2";
import ReactDOMClient from "https://dev.jspm.io/react-dom@17.0.2";
import ReactRouterDomClient from "https://dev.jspm.io/react-router-dom@5.2.0";
const React = ReactClient as any;
const ReactDOM = ReactDOMClient as any;
const ReactRouterDom = ReactRouterDomClient as any;
declare global {
namespace JSX {
interface IntrinsicElements {
[k: string]: any;
}
}
}
export { React, ReactDOM, ReactRouterDom };
next, make folder page and create file Home.tsx and About.tsx. for example i'm create 2 page.
Make page Home ./page/Home.tsx
import { React } from "./../deps-client.ts";
export const Home = ({ initData }: any) => {
return (
<h1>Welcome Home</h1>
)
}
Make page About ./page/About.tsx
import { React } from "./../deps-client.ts";
export const About = ({ initData }: any) => {
return (
<h1>Welcome About</h1>
)
}
next, make the router
Make router ./routes.tsx
import { Home } from "./page/Home.tsx";
import { About } from "./page/About.tsx";
export const routes = [
{
path: '/',
exact: true,
component: Home,
seo: {
title: 'Welcome to home',
description: 'This description sample for page Home'
}
},
{
path: '/about',
component: About,
seo: {
title: 'Welcome to about',
description: 'This description sample for page about'
}
}
]
next, make the component Navigation Bar
Make component Navigation Bar ./component/Navbar.tsx
import { React, ReactRouterDom } from "./../deps-client.ts";
const { Link } = ReactRouterDom;
export const Navbar = () => {
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
)
}
next, create app
Make app ./app.tsx
import { React, ReactRouterDom } from "./deps-client.ts";
import { routes } from './routes.tsx';
import { Navbar } from './component/Navbar.tsx';
const { Switch, Route } = ReactRouterDom;
export const App = ({ isServer, Component, initData }: any) => {
if (isServer) return (
<>
<Navbar />
<Component initData={initData} />
</>
)
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Navbar />
<Switch>
{routes.map((el, x) => {
return <Route
{...el}
key={x}
component={(props: any) => {
let _initData;
if ((window as any).__INITIAL_DATA__) {
_initData = initData;
delete (window as any).__INITIAL_DATA__;
}
if (el.seo) {
//@ts-ignore
document.title = el.seo.title;
}
return <el.component {...props} initData={_initData} />;
}}
/>
})}
</Switch>
</React.Suspense>
);
}
create client.tsx same as index.(js|ts) in CRA.
Make client.tsx ./client.tsx
import { React, ReactDOM, ReactRouterDom } from "./deps-client.ts";
import { App } from './app.tsx';
const { BrowserRouter } = ReactRouterDom;
// need hydrate when SSR
ReactDOM.hydrate(
<BrowserRouter>
<App initData={(window as any).__INITIAL_DATA__} />
</BrowserRouter>,
//@ts-ignore
document.getElementById('root')
);
next create the server side.
Make server.tsx ./server.tsx
import { Dero, ReactDOMServer } from "./deps-server.ts";
import { React, ReactRouterDom } from "./deps-client.ts";
import { routes } from './routes.tsx';
import { App } from './app.tsx';
const { StaticRouter, matchPath } = ReactRouterDom;
const { files } = await Deno.emit(
"./client.tsx",
{
check: false,
bundle: "module",
compilerOptions: {
lib: ["dom", "dom.iterable", "esnext"],
}
},
);
const BROWSER_PATH = '/dev-client.js';
class Server extends Dero {
constructor(){
super();
// build middleware and mutate body for react
this.use((req, res, next) => {
res.return.push((body) => {
if (React.isValidElement(body)) {
res.type("text/html");
const content = (ReactDOMServer as any).renderToString(body);
const seo = res.locals.seo;
return `
<!doctype html>
<html>
<head>
<title>${seo.title}</title>
<meta name="description" content="${seo.description}">
<script>window.__INITIAL_DATA__ = ${JSON.stringify(seo)};</script>
</head>
<body>
<div id="root">${content}</div>
<script src="${BROWSER_PATH}" defer></script>
</body>
</html>
`;
}
return;
});
next();
});
// get the client js
this.get(BROWSER_PATH, (req, res) => {
res.type('application/javascript').body(files["deno:///bundle.js"]);
});
// exact for all route
this.get("/*", (req, res) => {
const route = routes.find(r => matchPath(req.url, r));
if (route) {
res.locals.seo = route.seo;
return (
<StaticRouter location={req.url}>
<App isServer={true} Component={route.component} initData={{ seo: route.seo }} />
</StaticRouter>
);
}
res.status(404).body("Not Found");
});
}
}
await new Server().listen(3000, () => console.log('> Running on http://localhost:3000/'));
Now, run server
deno run --allow-read --allow-net --unstable server.tsx
I hope the code above can help you in finding Deno React SSR
Demo => https://deno-react-ssr.herokuapp.com
Repo => https://github.com/herudi/deno-react-ssr-movies
Thanks.. very grateful if you give a star or fork.
Top comments (0)