DEV Community

Cover image for Deno React SSR with React Router Dom
herudi
herudi

Posted on

Deno React SSR with React Router Dom

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";
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

Make page About ./page/About.tsx

import { React } from "./../deps-client.ts";

export const About = ({ initData }: any) => {
    return (
        <h1>Welcome About</h1>
    )
}
Enter fullscreen mode Exit fullscreen mode

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'
        }
    }
]
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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')
);
Enter fullscreen mode Exit fullscreen mode

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/'));

Enter fullscreen mode Exit fullscreen mode

Now, run server

deno run --allow-read --allow-net --unstable server.tsx
Enter fullscreen mode Exit fullscreen mode

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)