On this page
Build a Next.js App
Next.js is a popular framework for building server-side-rendered applications. It is built on top of React and provides a lot of features out of the box.
In this tutorial, we'll build a simple Next.js application and run it with Deno. The app will display a list of dinosaurs. When you click on one, it'll take you to a dinosaur page with more details. You can see the complete app on GitHub.
Create a Next.js app with Deno Jump to heading
Next provides a CLI tool to quickly scaffold a new Next.js app. In your terminal run the following command to create a new Next.js app with Deno:
deno run -A npm:create-next-app@latest
When prompted, select the default options to create a new Next.js app with TypeScript.
Then, cd
into the newly created project folder and run the following command
to install the dependencies with script execution allowed:
deno install --allow-scripts
Next.js has some dependencies that still rely on Object.prototype.__proto__
and requires CommonJS module support. To configure Deno for Next.js
compatibility, update your deno.json
file with the following configuration:
{
"nodeModulesDir": "auto",
"unstable": [
"unsafe-proto",
"sloppy-imports"
],
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"strict": true,
"jsx": "preserve"
},
"tasks": {
"dev": "deno run -A --unstable-detect-cjs npm:next@latest dev",
"build": "deno run -A --unstable-detect-cjs npm:next@latest build",
"start": "deno run -A --unstable-detect-cjs npm:next@latest start"
}
}
This configuration includes:
nodeModulesDir: "auto"
- Enables npm package lifecycle scriptsunstable: ["unsafe-proto", "sloppy-imports"]
- Required for Next.js compatibility--unstable-detect-cjs
flag - Enables CommonJS module detection for Next.js dependencies
Now you can serve your new Next.js app:
deno run dev
This will start the Next.js development server using Deno. The deno task dev
command runs the Next.js development server with the necessary flags for
CommonJS compatibility.
Visit http://localhost:3000 to see the app in the browser.
Add a backend Jump to heading
The next step is to add a backend API. We'll create a very simple API that returns information about dinosaurs.
We'll use Next.js's built in API route handlers to set up our dinosaur API. Next.js uses a file-system-based router, where the folder structure directly defines the routes.
We'll define three routes, The first route at /api
will return the string
Welcome to the dinosaur API
, then we'll set up /api/dinosaurs
to return all
the dinosaurs, and finally /api/dinosaur/[dinosaur]
to return a specific
dinosaur based on the name in the URL.
/api/ Jump to heading
In the src/app
folder of your new project, create an api
folder. In that
folder, create a route.ts
file, which will handle requests to `/api/.
Copy and paste the following code into the api/route.ts
file:
export function GET() {
return Response.json("welcome to the dinosaur API");
}
This code defines a simple route handler that returns a JSON response with the
string welcome to the dinosaur API
.
/api/data.json Jump to heading
In the api
folder, create a data.json
file, which will contain the hard
coded dinosaur data. Copy and paste
this json file
into the data.json
file.
/api/dinosaurs Jump to heading
In the api
folder, create a folder called dinosaurs
, in that create a
route.ts
file which will handle requests to /api/dinosaurs
. In this route
we'll read the data.json
file and return the dinosaurs as JSON:
import data from "./data.json" with { type: "json" };
export function GET() {
return Response.json(data);
}
/api/dinosaurs/[dinosaur] Jump to heading
And for the final route, /api/dinosaurs/[dinosaur]
, we'll create a folder
called [dinosaur]
in the dinosaurs
directory. In there, create a route.ts
file. In this file we'll read the data.json
file, find the dinosaur with the
name in the URL, and return it as JSON:
import data from "../../data.json" with { type: "json" };
type RouteParams = { params: Promise<{ dinosaur: string }> };
export const GET = async (_request: Request, { params }: RouteParams) => {
const { dinosaur } = await params;
if (!dinosaur) {
return Response.json("No dinosaur name provided.");
}
const dinosaurData = data.find((item) =>
item.name.toLowerCase() === dinosaur.toLowerCase()
);
return Response.json(dinosaurData ? dinosaurData : "No dinosaur found.");
};
Now, if you run the app with deno task dev
and visit
http://localhost:3000/api/dinosaurs/brachiosaurus
in your browser, you should
see the details of the brachiosaurus dinosaur.
Build the frontend Jump to heading
Now that we have our backend API set up, let's build the frontend to display the dinosaur data.
Define the dinosaur type Jump to heading
Firstly we'll set up a new type, to define the shape of the dinosaur data. In
the app
directory, create a types.ts
file and add the following code:
export type Dino = { name: string; description: string };
Update the homepage Jump to heading
We'll update the page.tsx
file in the app
directory to fetch the dinosaur
data from our API and display it as a list of links.
To execute client-side code in Next.js we need to use the use Client
directive
at the top of the file. Then we'll import the modules that we'll need in this
page and export the default function that will render the page:
"use client";
import { useEffect, useState } from "react";
import { Dino } from "./types";
import Link from "next/link";
export default function Home() {
}
Inside the body of the Home
function, we'll define a state variable to store
the dinosaur data, and a useEffect
hook to fetch the data from the API when
the component mounts:
const [dinosaurs, setDinosaurs] = useState<Dino[]>([]);
useEffect(() => {
(async () => {
const response = await fetch(`/api/dinosaurs`);
const allDinosaurs = await response.json() as Dino[];
setDinosaurs(allDinosaurs);
})();
}, []);
Beneath this, still inside the body of the Home
function, we'll return a list
of links, each linking to the dinosaur's page:
return (
<main>
<h1>Welcome to the Dinosaur app</h1>
<p>Click on a dinosaur below to learn more.</p>
<ul>
{dinosaurs.map((dinosaur: Dino) => {
return (
<li key={dinosaur.name}>
<Link href={`/${dinosaur.name.toLowerCase()}`}>
{dinosaur.name}
</Link>
</li>
);
})}
</ul>
</main>
);
Create the dinosaur page Jump to heading
Inside the app
directory, create a new folder called [dinosaur]
. Inside this
folder create a page.tsx
file. This file will fetch the details of a specific
dinosaur from the API and render them on the page.
Much like the homepage, we'll need client side code, and we'll import the modules we need and export a default function. We'll pass the incoming to the function and set up a type for this parameter:
"use client";
import { useEffect, useState } from "react";
import { Dino } from "../types";
import Link from "next/link";
type RouteParams = { params: Promise<{ dinosaur: string }> };
export default function Dinosaur({ params }: RouteParams) {
}
Inside the body of the Dinosaur
function we'll get the selected dinosaur from
the request, set up a state variable to store the dinosaur data, and write a
useEffect
hook to fetch the data from the API when the component mounts:
const selectedDinosaur = params.then((params) => params.dinosaur);
const [dinosaur, setDino] = useState<Dino>({ name: "", description: "" });
useEffect(() => {
(async () => {
const resp = await fetch(`/api/dinosaurs/${await selectedDinosaur}`);
const dino = await resp.json() as Dino;
setDino(dino);
})();
}, []);
Finally, still inside the Dinosaur
function body, we'll return a paragraph
element containing the dinosaur's name and description:
return (
<main>
<h1>{dinosaur.name}</h1>
<p>{dinosaur.description}</p>
<Link href="/">ðŸ Back to all dinosaurs</Link>
</main>
);
Add some styles Jump to heading
Let's add some basic styles to make the app look nicer. Update your
app/globals.css
file with the
styles from this file.
Run the app Jump to heading
Now you can run the app with deno run dev
and visit http://localhost:3000
in
your browser to see the list of dinosaurs. Click on a dinosaur to see more
details!
Deploy the app Jump to heading
Now that you have your working Next.js app, you can deploy it to the web with Deno DeployEA.
For the best experience, you can deploy your app directly from GitHub, which will set up automated deployments. Create a GitHub repository and push your app there.
Create a new GitHub repository, then initialize and push your app to GitHub:
git init -b main
git remote add origin https://github.com/<your_github_username>/<your_repo_name>.git
git add .
git commit -am 'my next app'
git push -u origin main
Once your app is on GitHub, you can deploy it on the Deno DeployEA dashboard. Deploy my app
For a walkthrough of deploying your app, check out the Deno Deploy tutorial.
🦕 Now you can build and run a Next.js app with Deno! To build on your app you
could consider adding a database
to replace your data.json
file, or consider
writing some tests to make your app reliable
and production ready.