Rethink React with Deno

How to setup a Deno server to perform ServerSideRendering with React. The Web is evolving in the right direction.

This article is just a POC, just a starting point.


Visit Deno land and follow instruction to install Deno on your platform.

If you want to install Deno on a Linux server, I would use the following command to install it for all users

curl -fsSL | sudo DENO_INSTALL=/usr/local sh

In my opinion Deno is the next big thing, once every 4 or 5 years I find myself saying

Wow! This technology is great, it is the right thing and will be enterprise in few years

It happened to me many times: with SSH, Git, Node.js, React, TypeScript… and now Deno. Usually I am right or almost right, not so far from the truth. For example at first I bet on Flow rather than TypeScript, then I realized soon that TypeScript was better, in particular because it was easier to export typings. Still the idea was the same, we needed typings in JavaScript land.

Now I am entering Deno land and I see an awesome tool that I already love 💙 but this is just the beginning of my journey.

Let me share how I started a minimal project with server rendering a React page server side.

Project files

Just few instructions, how to start the server, run tests and lint the code.

- Run server: `make start`
- Run tests: `make test`
- Lint code: `make lint`


	deno run --unstable --importmap=import_map.json --allow-net --allow-read --allow-env server.tsx
	deno test --unstable --importmap=import_map.json
	deno lint --unstable


Deno supports import maps.

This proposal allows control over what URLs get fetched by JavaScript import statements and import() expressions.

  "imports": {
    "std/": "",
    "react": "",
    "react-dom/server": ""


Yes, the article title starts with rethink, the server extension is tsx. It uses the experimental Deno.listen feature which uses a Rust implementation under the hood.

import React from "react";
import { renderToString } from "react-dom/server";

import { HomePage } from "./pages/home-page.tsx";

type HeaderValue = string | null;

interface RouteCaseArg {
  accept: HeaderValue;
  pathname: string;

async function startServer() {
  const port = 3000;
  const listener = Deno.listen({ port });

  while (true) {
    const conn = await listener.accept();

async function handleConnection(conn: Deno.Conn) {
  const http = Deno.serveHttp(conn);

  const event = await http.nextRequest();
  if (!event) return;

  const { request } = event;

  const url = new URL(request.url);
  const { pathname } = url;

  const accept = request.headers.get("accept");

  switch (true) {
    case isHomePage({ accept, pathname }): {
      const html = renderToString(<HomePage />);

        new Response(html, {
          headers: { "content-type": "text/html" },


    default: {
        new Response("not found", {
          headers: { "content-type": "text/plain" },

export function isHomePage({ accept, pathname }: RouteCaseArg) {
  return pathname === "/" && accept?.includes("text/html");

if (import.meta.main) {


Just a simple test.

import { assertEquals } from "std/testing/asserts.ts";

import { isHomePage } from "./server.tsx";

Deno.test("isHomePage", () => {
      pathname: "/",


Since Deno can import modules via URL, I strongly recommend to use kebab case as a filename convention: so home-page.tsx is better than HomePage.tsx.
import React from "react";

export function HomePage() {
  return (
    <html lang="en">
        <meta charSet="utf-8" />
      <body>It Works!</body>