DEV Community

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

Posted on

3 1

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.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more