Guide to server-side rendering with Deno and React
Published: 2021-06-10 18:35:40

---
title: Guide to server-side rendering with Deno and React
published: true
description: In this tutorial, we will take a look at server-side rendering with Deno and React. To get a proper hang of the fundamental concepts, we will start by discovering what Deno is, and how it compares to Node in terms of security, package management, and so on.
tags: 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h6lvgqh0t98511mpud1p.png
canonical_url: https://www.sanity.io/guides/server-side-rendering-deno-react
---

**Summary**

In this tutorial, we will take a look at server-side rendering with Deno and React. To get a proper hang of the fundamental concepts, we will start by discovering what Deno is, and how it compares to Node in terms of security, package management, and so on.

**Introduction**

"Using Node is like nails on a chalkboard."

- Ryan Dahl, creator of Node.js at [2018 JSConf EU](https://www.youtube.com/watch?v=M3BM9TB-8yA).

While Node is still considered a great server-side JavaScript runtime and used by a large community of Software Engineers, even its creator admits that it leaves a lot to be desired. That's why he created [Deno.js](https://github.com/denoland/deno).

**What is Deno?**

As stated on its [official website](https://deno.land/), Deno is a simple and secured runtime for JavaScript and TypeScript. According to Ryan, it was created to fix some of the design mistakes made when Node was created. It is gaining popularity and gradually getting adopted by the JavaScript and TypeScript community.


**Why Deno?**

There are quite a few reasons to choose Deno over Node.js. 

- The first is package management. With Node.js, packages dependencies are declared in a `package.json` file. The packages are then downloaded to the `node_modules` folder. This can make the project bloated, unwieldy, and sometimes difficult to manage. Deno takes a different approach to handle package dependencies. In Deno, package dependencies are installed directly from a provided URL and stored centrally. Additionally, every package is cached on the hard drive after installation which means they don’t have to be re-downloaded for subsequent projects.
- The second key advantage is security. By default, Deno takes an opt-in approach where you have to specify the permissions for the application. This means that Deno scripts cannot (for example) access the hard drive, environment, or open network connections without permission. This not only prevents unwanted side-effects in our application but also reduces the impact of potentially malicious packages on our application.
- Deno also provides TypeScript support by default which means that one can take advantage of all the benefits of static typing without any additional configuration.

## What is server-side rendering?

Server-side rendering (SSR) is the process of rendering web pages on a server and passing them to the browser instead of rendering them in the browser (client-side).

This has the advantage of making applications more performant as most of the resource-intensive operations are handled server-side. It also makes Single Page Applications (SPA) more optimized for SEO as the SSR content can be crawled by search engine bots.

## What we will build

In this tutorial, we will build a movie application to show some bestselling movies and A-list actors. [Sanity.io](http://sanity.io) will be used for the backend to manage contents, which includes creating, editing, and updating data for the project. While Deno and React will be used for the front end and server-side rendering respectively. [Tailwind CSS](https://tailwindcss.com/) will be used to style the application UI.

![Gif image of the movie app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aq081vm057htqw75jq04.gif)


## Prerequisites

To keep up with the concepts that will be introduced in this article, you will need a fair grasp of ES6. You will also need to have a basic understanding of [React](https://www.freecodecamp.org/news/start-your-journey-into-the-world-of-react-by-learning-these-basics-d6e05d3655e3/) and [TypeScript](https://www.freecodecamp.org/news/learn-typescript-with-this-crash-course/).

Additionally, you will also need to have the following installed locally. Please click on each one for instructions on how to download:

1. [Deno](https://deno.land/#installation)
2. [Node Package Manager (NPM)](https://www.npmjs.com/get-npm)
3. [Sanity CLI](https://www.sanity.io/docs/getting-started-with-sanity-cli)
4. A code editor of your choice. For example, VS Code.

## Getting started

To get started, create a new directory in your terminal. 

```bash
mkdir sanity-deno

cd sanity-deno

```

In the `sanity-deno` directory, create a new directory called `frontend`. This will hold the components for our React UI along with the code for handling SSR using Deno. 

```bash
mkdir frontend

```

In the `frontend` directory, create two new directories, `ssr` and `components`. 

```bash
mkdir frontend/ssr frontend/components
```

## Setting up SSR

In the `ssr` directory, create a new file named `server.tsx` to hold our server side code. 

```bash
cd frontend/ssr

touch server.tsx
```

For our Deno server, we are using the [Oak](https://deno.land/x/oak@v7.3.0) middleware framework, which is a framework for Deno's [HTTP server](https://doc.deno.land/https/deno.land/std/http/mod.ts). It is similar to [Koa](https://koajs.com/) and [Express](https://expressjs.com/) and the most popular choice for building web applications with Deno. In `server.tsx`, add the following code:

```tsx
// frontend/ssr/server.tsx

import { Application, Router } from 'https://deno.land/x/oak@v7.3.0/mod.ts';

const app = new Application();
const port: number = 8000;

const router = new Router();
router.get("/", (context) => {
    context.response.body =
        `<!DOCTYPE html>
				 <html lang="en">
				 <head>
			   <meta charset="UTF-8">
			   <meta name="viewport" content="width=device-width, initial-scale=1.0">
				 <link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet">
			   <title>Sanity <-> Deno</title>
				 </head>
				 <body>
			   <div id="root">
	       <h1> Deno and React with Sanity</h1>
				 </div>
				 </body>
				 </html>`;
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen({port});
console.log(`server is running on port: ${port}`);
```

Using the `Application` and `Router` modules imported from the Oak framework, we create a new application that listens to requests on port `8000` of the local workstation. We've also added an index route that returns the HTML content to be displayed by the browser. Tailwind is added to the project via the link declared in the head of the HTML element.

With this in place we can test our deno server. Run the following command **(within the `ssr` directory).**

```bash
deno run --allow-net server.tsx
```

In this command, we specify `server.ts` as the entry point for the deno application. We also use the `--allow-net` command to grant the application permission to access network connections.  

Navigate to [http://localhost:8000](http://localhost:8000/) and you will see:

![Deno application homepage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1bn7mnx5hehy6vntmf5.png)


Stop your application for now by pressing `Ctrl + C`. 

## Adding other dependencies

In the `frontend` directory, create a file named `dep.ts`

```json
touch frontend/dep.ts 
```

The `dep.ts` file will contain the frontend project dependencies. This is in line with the deno convention for [managing dependencies](https://deno.land/manual@v1.9.2/examples/manage_dependencies).  Our application will import the following packages:

1. React, ReactDOM and ReactDOMServer for our React UI. 
2. React Router to handle routing between components
3. The [Sanity image-url builder](https://www.sanity.io/docs/image-url)

Add the following dependencies to `dep.ts`

```tsx
// @deno-types="https://denopkg.com/soremwar/deno_types/react/v16.13.1/react.d.ts"
import React from "https://jspm.dev/react@17.0.2";
// @deno-types="https://denopkg.com/soremwar/deno_types/react-dom/v16.13.1/server.d.ts"
import ReactDOMServer from "https://jspm.dev/react-dom@17.0.2/server";
// @deno-types="https://denopkg.com/soremwar/deno_types/react-dom/v16.13.1/react-dom.d.ts"
import ReactDOM from "https://jspm.dev/react-dom@17.0.2";
import imageURLBuilder from "https://cdn.skypack.dev/@sanity/image-url@0.140.22";
export {
  BrowserRouter,
  StaticRouter,
  Link,
  Route,
  Switch,
} from "https://cdn.skypack.dev/react-router-dom";
export { React, ReactDOM, ReactDOMServer, imageURLBuilder };
```

We use the `@deno-types` annotation to specify a URL where the React and ReactDOMServer types can be found. You can read more about providing types when importing [here](https://deno.land/manual@v1.9.2/typescript/types#providing-types-when-importing).

## Creating the app component

Next, create the `App` component. In the `frontend/components` directory, create a file named `App.tsx`.

In `App.tsx` add the following:

```tsx
// frontend/components/App.tsx

import { React } from "../dep.ts";

const App = () => <h1> App Component for Deno and React with Sanity</h1>;

export default App;
```

Here, we are still returning a `h1` tag but this will serve as the entry point for React as we build further. 

## Rendering components via SSR

At the moment, we're returning HTML from the deno server but we also need to render our react components server side. To do this, update `ssr/server.tsx` to match the following:

```tsx
// frontend/ssr/server.tsx

import { Application, Router } from 'https://deno.land/x/oak@v7.3.0/mod.ts';
import { ReactDOMServer, React } from "../dep.ts";
import App from "../components/App.tsx";

const app = new Application();
const port: number = 8000;

const router = new Router();
router.get("/", (context) => {
  context.response.body = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
		<link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet">
    <title>Sanity <-> Deno</title>
</head>
<body >
    <div id="root">${ReactDOMServer.renderToString(<App />)}
    </div>
</body>
</html>`;
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen({ port });
console.log(`server is running on port: ${port}`);
```

Using the `renderToString` function, we are able to render our `App` component in the body of the HTML returned by our deno server. 

Restart the application using the `npm run start` command and you will see the new body is reflected. 

![Content of AppComponent](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/genoslpy5c9mucpu170g.png)

With this in place, we actually have our SSR set up. Let's add some content to our application by building the backend with Sanity. 

## Setting up the backend

In the `sanity-deno` directory, create a new directory called `backend`. Initialize a new Sanity project in the `backend` directory.

```bash
mkdir sanity-deno/backend

cd backend 

sanity init
```

You will be  prompted to provide some information. Proceed as follows:

1. Select the `Create new project` option
2. Name the project `react_deno-backend`
3. Use the default dataset configuration (press `Y`)
4. Select the project output path (by default it would be the `backend` directory)
5. Select the movie project (schema + sample data) option. Using the arrow keys, navigate to that option and press enter when it turns blue.
6. Upload a sampling of sci-fi movies to your dataset on the hosted backend (press `Y`)

The Sanity CLI will bootstrap a project from the movie template, link the needed dependencies and populate the backend with some science fiction movies. Once the process is completed, you can run your studio. By default, the studio runs at [http://localhost:3333](http://localhost:3333/).

```bash
sanity start
```

Your studio will be similar to the screenshot below.

![Sanity studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjbcnbyagaaz1j3z85he.png)

To connect our frontend application with Sanity, we need the `projectId` and dataset used for our Sanity project. You can find this in the `api` node of `backend/sanity.json`. Make a note of it as we will be using it later on. 

We also need to [enable CORS](https://www.sanity.io/docs/cors) on our backend to ensure that it honors requests made by the application. By default, the only host that can connect to the project API is the sanity studio ([http://localhost:3333](http://localhost:3333/)). Before we can make requests to the API, we need to add the host for our application ([http://localhost:8000/](http://localhost:8000/)) to the permitted origins. To do this, open your [Sanity Content Studio](https://manage.sanity.io/projects). This will show you all the Sanity projects you have initialized. Select the project we are working on (`react_deno-backend`) and click on the `Settings` tab. Click on the `API` menu option. In the  CORS Origins section, click the `Add new origin` button.  In the form that is displayed, type `http://localhost:8000` as the origin. Click the `Add new origin` button to save the changes made.

## Creating a client for sanity studio

We need a client that will help us connect and retrieve data from our Sanity studio. In the `frontend` directory, create a new file called `SanityAPI.ts`. 

```bash
touch frontend/SanityAPI.ts
```

[Rune Botten](https://www.sanity.io/exchange/community/runeb) created a sample client we can use with deno [here](https://gist.github.com/runeb/2671cac4af572751f6108f53ab926032). We will use this as a foundation and make some slight modifications to suit our use case. Add the following code to `frontend/SanityAPI.ts`.

```tsx
// frontend/SanityAPI.ts

type SanityClientOptions = {
  projectId: string;
  dataset: string;
	apiVersion: string;
  token?: string;
  useCdn?: boolean;
};

type QueryParameters = Record<string, string | number>;

const sanityCredentials = {
    projectId: "INSERT_YOUR_PROJECT_ID_HERE",
    dataset: "production",
};

const sanityClient = (options: SanityClientOptions) => {
  const { useCdn, projectId, dataset, token, apiVersion } = options;
  const hasToken = token && token.length > 0;
  const baseHost = useCdn && !hasToken ? "apicdn.sanity.io" : "api.sanity.io";
  const endpoint = `https://${projectId}.${baseHost}/v${apiVersion}/data/query/${dataset}`;

  // Parse JSON and throw on bad responses
  const responseHandler = (response: Response) => {
    if (response.status >= 400) {
      throw new Error([response.status, response.statusText].join(" "));
    }
    return response.json();
  };

  // We need to prefix groq query params with `$` and quote the strings
  const transformedParams = (parameters: QueryParameters) =>
    Object.keys(parameters).reduce<QueryParameters>((prev, key) => {
      prev[`$${key}`] = JSON.stringify(parameters[key]);
      return prev;
    }, {});

  return {
    fetch: async (query: string, parameters?: QueryParameters) => {
      const urlParams = new URLSearchParams({
        query,
        ...(parameters && transformedParams(parameters)),
      });

      const url = new URL([endpoint, urlParams].join("?"));
      const request = new Request(url.toString());

      if (hasToken) {
        request.headers.set("Authorization", `Bearer ${token}`);
      }

      return (
        fetch(request)
          .then(responseHandler)
          // The query results are in the `result` property
          .then((json) => json.result)
      );
    },
  };
};

export const runQuery = async (
  query: string,
  callback: (json: any[]) => void
) => {
  const client = sanityClient({
    ...sanityCredentials,
    useCdn: false,
		apiVersion: "2021-03-25"
  });
  await client.fetch(query).then(callback);
};

export const urlFor = (source: any) =>
  imageURLBuilder(sanityCredentials).image(source);
```

Note the `runQuery` function. We provide a [GROQ](https://www.sanity.io/docs/overview-groq) query as the first parameter, the second is a callback function to be executed on the response to the provided query. This function will be called anytime we need to pull data from our backend. 

We also added a helper function named `urlFor` which will be used to generate image URLs for images hosted on Sanity. 

Lastly, don't forget to replace the `INSERT_YOUR_PROJECT_ID_HERE` placeholder with your `projectId` as obtained earlier.

## Building the components

Our application will display the movies and actors saved on our Sanity Content Lake. In the `components` directory, create a directory named `movie` to hold movie-related components and another named `actor` to hold actor-related components. 

```bash
mkdir frontend/components/actor frontend/components/movie
```

In the `movie` directory, create two files named `Movies.tsx` and `Movie.tsx`. In the `Movies.tsx`, add the following:

```tsx
// frontend/components/movie/Movies.tsx

import { React } from "../../dep.ts";
import Movie from "./Movie.tsx";
import { runQuery } from "../../SanityAPI.ts";

const { useState, useEffect } = React;
const Movies = () => {
  const [movies, setMovies] = useState<any>([]);
  const updateMovies = (movies: any[]) => {
    setMovies(movies);
  };
  useEffect(() => {
    const query = `*[_type == 'movie']{_id, title, overview, releaseDate, poster}`;
    runQuery(query, updateMovies);
  }, []);

  return (
    <div className="container mx-auto px-6">
      <h3 className="text-gray-700 text-2xl font-medium">
        Bestselling Sci-fi Movies
      </h3>
      <div className="p-10 grid grid-cols-1 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-3 gap-5">
        {movies.map((movie: any) => (
          <Movie key={movie._id} {...movie} />
        ))}
      </div>
    </div>
  );
};

export default Movies;
```

In this component, we make a GROQ request to the Sanity API using the helper client we created earlier. In our query, we request for the id, title, overview, release date and poster image for all the movies on our backend.

Next, add the following to `Movie.tsx`.

```tsx
// frontend/components/movie/Movie.tsx

import { React } from "../../dep.ts";
import { urlFor } from "../../SanityAPI.ts";

const Movie:React.FC<any> = ({ title, overview, releaseDate, poster }) => {
  return (
      <div className="max-w-xs rounded overflow-hidden shadow-lg my-2">
        <img
          src={`${urlFor(poster.asset._ref)}`}
          alt={title}
        />
        <div className="px-6 py-4">
          <h3 className="text-xl mb-2 text-center">{title}</h3>
          <p className="text-md justify-start">
            {overview[0].children[0].text}
          </p>
        </div>
        <div className="align-bottom">
          <p className="text-gray text-center text-xs font-medium my-5">
            Released on {new Date(releaseDate).toDateString()}
          </p>
        </div>
      </div>
  );
};

export default Movie;
```

Using Tailwind, we design a card that holds the poster image for the movie with the image URL generated by the `urlFor` helper function we declared earlier. We are also displaying the other details provided by the `Movies` component. 

As we did for the movies, create two files named `Actor.tsx` and `Actors.tsx` in the `actor` directory. Add the following to `Actor.tsx`

```tsx
// frontend/components/actor/Actor.tsx

import { React } from "../../dep.ts";
import { urlFor } from "../../SanityAPI.ts";

const Actor: React.FC<any> = ({ name, image }) => {
  const defaultImageURL =
    "https://images.vexels.com/media/users/3/140384/isolated/preview/fa2513b856a0c96691ae3c5c39629f31-girl-profile-avatar-1-by-vexels.png";
  return (
    <>
      <div className="max-w-xs rounded overflow-hidden shadow-lg my-2">
        <img
          className="w-full"
          src={`${image ? urlFor(image.asset._ref) : defaultImageURL}`}
          alt={name}
        />
        <div className="px-6 py-4">
          <h3 className="text-xl mb-2 text-center">{name}</h3>
        </div>
      </div>
    </>
  );
};

export default Actor;
```

For actors, we only display their picture and name. Because we don't have pictures of all the actors in our backend, we specify an image url to be used if a picture is not available. 

Next, in the `Actors.tsx` file, add the following:

```tsx
// frontend/components/actor/Actors.tsx

import { React } from "../../dep.ts";
import { runQuery } from "../../SanityAPI.ts";
import Actor from "./Actor.tsx";

const { useState, useEffect } = React;
const Actors = () => {
  const [actor, setActors] = useState<any>([]);
  const updateActors = (actors: any[]) => {
    setActors(actors);
  };
  useEffect(() => {
    const query = `*[_type == 'person']{ _id, name, image}`;
    runQuery(query, updateActors);
  }, []);

  return (
    <div className="container mx-auto px-6">
      <h3 className="text-gray-700 text-2xl font-medium">A-List Movie Stars</h3>
      <div className="p-10 grid grid-cols-1 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-3 gap-5">
        {actor.map((actor: any) => (
          <Actor key={actor._id} {...actor} />
        ))}
      </div>
    </div>
  );
};

export default Actors;
```

Here, we make a GROQ query to retrieve all the actors in our backed and map through the returned array, rendering and `Actor` component for each object in the array. 

With these in place, we can update our `App.tsx` file as follows:

```tsx
// frontend/components/App.tsx

import { React, Link, Route, Switch } from "../dep.ts";
import Movies from "./movie/Movies.tsx";
import Actors from "./actor/Actors.tsx";

const App = () => {
  return (
    <div className="bg-white">
      <header>
        <div className="container mx-auto px-6 py-3">
          <nav className="sm:flex sm:justify-center sm:items-center mt-4">
            <div className="flex flex-col sm:flex-row">
              <Link
                to="/movies"
                className="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0"
              >
                Movies
              </Link>
              <Link
                to="/actors"
                className="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0"
              >
                Actors
              </Link>
            </div>
          </nav>
        </div>
      </header>
      <main className="my-8">
        <Switch>
          <Route path="/" exact>
            <Movies />
          </Route>
          <Route path="/movies">
            <Movies />
          </Route>
          <Route path="/actors">
            <Actors />
          </Route>
        </Switch>
      </main>
    </div>
  );
};

export default App;
```

 Here, we implement the client side routing for the React UI as well as style the container for the components. 

## Hydrating our application

At the moment, our application HTML is generated server-side. However, our application will not have the desired functionality because our JavaScript functionality isn't loaded on our page -clicking any link will trigger another render server-side instead of the routing being handled client-side. To fix this, we need to hydrate our React application as well as provide the JavaScript to be loaded client-side. This way, once the index page is loaded, the JavaScript will take over and handle everything else. 

Let's start by creating the JavaScript to be used client-side. In the `ssr` directory, create a new file called `client.tsx`. In it, add the following:

```tsx
// frontend/ssr/client.tsx

import { React, ReactDOM, BrowserRouter } from "../dep.ts";
import App from "../components/App.tsx";

ReactDOM.hydrate(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
```

Notice, we've also added a `BrowserRouter` to handle client-side routing once the page is loaded. 

Next, we need to modify the `ssr/server.tsx` file to include the JavaScript bundle created (by Deno) from the `ssr/client.tsx` file. We also need to add a route that will allow the browser to download the bundled Javascript to be loaded client-side. 

Update `ssr/server.tsx` to match the following:

```tsx
// frontend/ssr/server.tsx

import { Application, Router } from 'https://deno.land/x/oak@v7.3.0/mod.ts';
import {
  React,
  ReactDOMServer,
  StaticRouter,
} from "../dep.ts";
import App from "../components/App.tsx";

const app = new Application();
const port: number = 8000;

const jsBundlePath = "/main.js";

const { diagnostics, files } = await Deno.emit("./ssr/client.tsx", {
  bundle: "esm",
  compilerOptions: { lib: ["dom", "dom.iterable", "esnext"] },
});

console.log(diagnostics);

const router = new Router();
router
  .get("/", (context) => {
    const app = ReactDOMServer.renderToString(
      <StaticRouter location={context.request.url} context={context}>
        <App />
      </StaticRouter>
    );
    context.response.type = "text/html";
    context.response.body = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet">
    <title>Sanity <-> Deno</title>
    <script type="module" src="${jsBundlePath}"></script>
</head>
<body>
    <div id="root">${app}
    </div>
</body>
</html>`;
  })
  .get(jsBundlePath, (context) => {
    context.response.type = "application/javascript";
    context.response.body = files["deno:///bundle.js"];
  });

app.addEventListener("error", (event) => {
  console.error(event.error);
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen({ port });
console.log(`Server is running on port ${port}`);
```

We specify the bundled Javascript path as `/main.js` and added it as a route to our oak application.

Next using the `Deno.emit()` compiler API, we transpile and bundle  our `client.tsx` file into JavaScript that can be run on the browser. We also update the JSX for the index page to include a script tag pointing to our bundle path. 

The `Deno.emit()` is currently marked as unstable which means we need to give our application explicit permission to perform this operation. We do this with the `--unstable` flag. 

With this in place, our server side rendered app is ready for a test run. Start your Sanity project (if you stopped it).

```bash
cd backend

sanity start
```

Then run your deno application using the command below

```bash
cd frontend

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

Navigate to [http://localhost:8000/](http://localhost:8000/) to see your application in action. 

![Gif image of the movie app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aq081vm057htqw75jq04.gif)

## Conclusion

Deno is relatively new in the world of web development but it is gradually gaining popularity and acceptance due to some of its advantages over Node.js. As seen in this tutorial, we learned about Deno and why it was built. We then proceeded to build a server-side rendered movie application using Deno and React. 

For proper content management, we leverage the existing infrastructure put in place by Sanity to build the backend of the application. I hope this tutorial has given you some insight into what Deno is, and how to combine it with a frontend framework such as React.

The complete source code for the application built in this tutorial can be found [here on GitHub](https://github.com/yemiwebby/sanity-deno). Happy coding!