Writing a React SSR app in Deno
Tags: react, deno, javascript, typescript
Published: 2020-05-21 13:29:21

> As featured in [Open JS World 2020](https://www.youtube.com/watch?v=doug6st5vAs) by Ryan Dahl.

[Deno](https://deno.land/) v1 has shipped and is causing a real buzz in the JavaScript community.

For those that haven't come across it yet, Deno is a new runtime for JavaScript and TypeScript outside of the web browser. It based on the V8 JavaScript Engine, written in Rust and was created by Ryan Dahl, the original founder of Node.js.

If you want to find out more about Deno and it's mission, check out the [Deno 1.0 launch blogpost](https://deno.land/v1) written by the creators.

Background over, let's begin with writing our React SSR application in Deno!

# Installation

Deno can be installed using all the main package installers as well using the official installer scripts. Here are some of the main ways to install:

*Shell (Mac, Linux):*

```bash
curl -fsSL https://deno.land/x/install/install.sh | sh
```

*PowerShell (Windows):*

```pwsh
iwr https://deno.land/x/install/install.ps1 -useb | iex
```

*Homebrew (Mac):*

```console
brew install deno
```

*Chocolatey (Windows):*

```console
choco install deno
```

Head over to the [Deno installation page](https://deno.land/#installation) for other installation methods and further details!

# Getting started

> Please note that Deno is rapidly developing, which can quickly make articles like this one become outdated. Always check to see if there are newer versions of the modules used to ensure the code works for the latest version of Deno.

Having installed Deno you can now run make use of the `deno` command! Use `deno help` to explore the commands on offer. We'll be using this command to run our React SSR app later on.

But first let's create a project!

In a new project directory let's create three files:

```
.
├── app.tsx
├── client.tsx
└── server.tsx
```

`app.tsx` will contain our React component code, `server.tsx` will hold all of our server code and `client.tsx` will act as our entrypoint to the client-side bundle. Be careful to get the correct file extensions!

## Writing our client-side bundle

In the `client.tsx` file, add the following code to set up our client-side entrypoint:

```ts
import React from "https://dev.jspm.io/react@16.13.1";
import ReactDOM from "https://dev.jspm.io/react-dom@16.13.1";
import App from "./app.tsx";

(ReactDOM as any).hydrate(
  <App />,
  //@ts-ignore
  document.getElementById("root"),
);
```

First we import React and React DOM like we're used to in any React app, but instead of importing from `"react"`, we're importing it from a url...!?

That's right, in Deno you can import modules from any URL and relative or absolute file path that exports a module. This means you can easily pull in any code from the web, e.g. gists, GitHub code and are no longer tied to versions that have been released - if there's something on a `main` branch that you can't wait to try, you can just import it!

Here we are importing React and React DOM from [JSPM](https://jspm.org/), but you could equally use any CDN that provides React as an ES Module. Check out the [Deno website](https://deno.land/#third-party-modules) for CDN alternatives.

Following our imports of React libraries we import our App component (yet to be written!) and finally set up the code to render our application, using the React DOM [hydrate](https://reactjs.org/docs/react-dom.html#hydrate) method.

Now let's write our first React component in Deno!

## Writing the React component

Our `app.tsx`:

```ts
// @deno-types="https://raw.githubusercontent.com/Soremwar/deno_types/4a50660/react/v16.13.1/react.d.ts"
import React from "https://dev.jspm.io/react@16.13.1";

const App = () => {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <h1>Hello Deno Land!</h1>
      <button onClick={() => setCount(count + 1)}>Click the 🦕</button>
      <p>You clicked the 🦕 {count} times</p>
    </div>
  );
};

export default App;
```

There's a lot going on here, so let's break it down -

First we import `React` like we're used to in any React app, but notice we also are using a `@deno-types` hint comment. This allows us to inform deno of where to find the TypeScript types for the imported module - neat huh?

You can choose to omit this type hint, but TypeScript will require you to provide the types yourself (imported or custom written). Alternatively, you have avoid using TypeScript altogether simply by changing the file extension to `.jsx`. Deno supports both TypeScript and JavaScript out-of-the-box!

Lastly we create a small React component called `App` which uses hooks to create a button click counter - simple! Overall, there isn't much difference to writing a React component in NodeJS.

## Writing the server

For the server we will be using the Deno web framework [Opine](https://github.com/asos-craigmorten/opine), which is a port of the ExpressJS web framework that is commonly used in NodeJS.

Here's the code we'll be using for `server.tsx`:

```ts
// @deno-types="https://raw.githubusercontent.com/Soremwar/deno_types/4a50660/react/v16.13.1/react.d.ts"
import React from "https://dev.jspm.io/react@16.13.1";
import ReactDOMServer from "https://dev.jspm.io/react-dom@16.13.1/server";
import { opine } from "https://deno.land/x/opine@0.25.0/mod.ts";
import App from "./app.tsx";

/**
 * Create our client bundle - you could split this out into
 * a preprocessing step.
 */
const [diagnostics, js] = await Deno.bundle(
  "./client.tsx",
  undefined,
  { lib: ["dom", "dom.iterable", "esnext"] },
);

if (diagnostics) {
  console.log(diagnostics);
}

/**
 * Create our Opine server.
 */
const app = opine();
const browserBundlePath = "/browser.js";

const html =
  `<html><head><script type="module" src="${browserBundlePath}"></script><style>* { font-family: Helvetica; }</style></head><body><div id="root">${
    (ReactDOMServer as any).renderToString(<App />)
  }</div></body></html>`;

app.use(browserBundlePath, (req, res, next) => {
  res.type("application/javascript").send(js);
});

app.use("/", (req, res, next) => {
  res.type("text/html").send(html);
});

app.listen({ port: 3000 });

console.log("React SSR App listening on port 3000");
```

Here's what is going on:

1. First we import our main dependencies of `React`, `ReactDOMServer` and the [Opine](https://github.com/asos-craigmorten/opine) web framework.
1. We then import the React app we just created, being careful to include the `.tsx` extension - file extensions are required by Deno unlike in NodeJS.
1. First we use the `Deno.bundle()` method to create our client-side JavaScript bundle from our application.
1. Next we create an [Opine](https://github.com/asos-craigmorten/opine) app, much like you would do with ExpressJs, and define some routes: one to serve a simple HTML page containing our rendered app, and another `/browser.js` route to server our app's client-side bundle so we can hydrate the React application on the client.
1. Finally we start the server using the `listen()` method on port `3000`.

And that's it! We're now ready to run our React application :tada:.

# Running our React SSR application

We can now run our React SSR application using the following `deno` command:

```console
deno run --allow-net --allow-read --unstable ./server.tsx
```

Note the use of the various flags! A major difference between Deno and NodeJS is that Deno was build with security in mind. Any action that needs to access the web, read or write to files, or even consume environment variables needs to have the permission granted before Deno will allow it.

To find out more, check out the [Deno permissions](https://deno.land/manual/getting_started/permissions) section of the Deno Manual.

For our example application, we require `--allow-net` so that our server is allowed to access the network, `--allow-read` is required by the Opine framework (so it can read templates if make use of it's "views" feature), and we also need the `--unstable` flag to make use of the `Deno.bundle()` API, which is still in preview.

Head over to <http://localhost:3000/> and voila! You should now see your React SSR application running in your browser. :smile:

# Next steps

This is just a basic server and app setup, but by now you should hopefully see that there isn't too much to do to convert your existing applications over to Deno.

That's all gang! Would love to hear your thoughts and how you're getting on with Deno - drop your comments below!

---

_Update 29-06-2020: Deno is progressing quickly and all the aforementioned bugs with JSX are resolved, so I have removed references to them in this article!_

_Update 20-07-2020: If you followed this tutorial prior to the release of Deno 1.2.0 you will find that after upgrading there are several url related errors. Deno 1.2.0 brought about a breaking change for the std library so any module using std version before 0.61.0 may well error! Generally try seeing if modules you are using can be upgraded to a later version, and if that doesn’t fix it then try opening an issue on the repo you are having issues with!_