Deno logoDeno
Whole Website in a Single JavaScript File

A Whole Website in a Single JavaScript File


This site is a pretty standard demo website; a site with links to different pages. Nothing to write home about except that the whole website is contained within a single JavaScript file, and is rendered dynamically, just in time, at the edge, close to the user.

The routing is fairly minimal: we use the router module which uses URLPattern under the hood for pattern matching.

/** @jsx h */
/// <reference no-default-lib="true"/>
/// <reference lib="dom" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
import { router } from "https://crux.land/router@0.0.11";
import { h, ssr } from "https://crux.land/nanossr@0.0.4";

const render = (component) => ssr(() => <App>{component}</App>);

serve(router(
  {
    "/": () => render(<Landing />),
    "/stats": () => render(<Stats />),
    "/bagels": () => render(<Bagels />),
  },
  () => render(<NotFound />),
));

We have 3 paths: / for the main page, and/stats for some statistics, and /bagels to display various bagels that we sell. We also have a fallback handler (the last argument in the snippet), which will be called when none of the paths match, acting as a 404 page. These are served using the serve function from the standard library.

For rendering of the JSX, we use nanossr, which is a tiny server-side renderer that uses twind under the hood for styling. This is invoked by using the ssr function, to which a callback is passed which should return JSX. The ssr function returns a Response. We create a render function to remove the need for larger duplication of code, just to tidy things up.

But before any of this, you probably noticed some funny looking comments. Let's go through them:

  • /** @jsx h */: this pragma tells Deno which function we want to use for transpiling the JSX.
  • /// <reference no-default-lib="true"/>: Deno comes with various TypeScript libraries enabled by default; with this comment, we tell it to not use those, but instead only the ones we specify manually.
  • /// <reference lib="dom" />, /// <reference lib="dom.asynciterable" />, and /// <reference lib="deno.ns" />: these libraries provide various typings. The dom one gives us various typings related to the DOM, and dom.asynciterable defines functions that need async iterators as these are not included in the normal dom library as of writing. The deno.ns library gives us typings for any of the functions and classes under the Deno.* namespace, like Deno.readFile. You can read more about the various libraries available in the manual.

Let's look at some actual code; in the code snippet earlier you might have noticed that we wrap all route components in an App component. This is defined as:

function App({ children }) {
  return (
    <div class="min-h-screen">
      <NavBar />
      {children}
    </div>
  );
}

It's fairly compact: it returns a div and inside that we have our NavBar which is our menu navigation at the top of the page. We're doing templating server-side, sharing components between different pages using the same language one would do this client-side.

Hosted on Deno Deploy, this little website is able to acheive a perfect pagespeed score. Serviced from an anycast IP address over encrypted HTTP from 29 data centers around the world, the site is fully managed and will be available indefinitely at zero cost.

Everything mentioned here can be viewed on a playground.