Static Sites with Next.js 9.3 and Prisma

JW

Jonathan Wong / April 08, 2020

8 min read––– views

Static sites are all the rage – and for a good reason.

The JAMstack (i.e., JavaScript, APIs, and Markup) has emerged as an attractive alternative to traditional web apps. Static apps have many benefits:

  • Globally scalable
  • Save money hosting static files
  • Faster performance by serving cached assets from a CDN
  • No need to worry about server or database vulnerabilities
  • Better developer experience – no complicated deployment process

What if you have dynamic needs? With Next.js 9.3, it's no problem.

Next.JS 9.3#

Before 9.3, Next.js was mostly used for server-side rendered (SSR) React apps. It solved a few common issues with most client-side rendered (CSR) React apps, including search engine optimization (SEO), routing, and code splitting. There was only one issue. SSR requires, well... a server. This limits your deployment options and increases costs. That's where Next.js 9.3 came in.

With the release of 9.3, Next.js is now a complete static-site generator (SSG). Using two new Next.js functions getStaticProps and getStaticPaths, we can take dynamic data and build static sites. Incredible!

Here's the special sauce: getStaticProps is only ran at build time – it's not even included in the browser JS bundle. That means you can write server-side code directly, including direct database queries. Enter Prisma.

Prisma#

Prisma makes database access easy. With auto-generated and type-safe queries based on your database schema, it's easier than ever to manage your data. Whether you have an existing database or you're starting from scratch, Prisma has you covered. It currently supports MySQL, SQLite, and Postgres. For this tutorial, I'll be using SQLite.

When combining Prisma with Next.js 9.3, you can skip the create/update/delete API boilerplate and directly query the database. Less code means less bugs.

Getting Started#

Okay, enough talking. Let's build an app! Here's the final result.

  • The entire site is static and can be hosted anywhere
  • Data is read from the database at build time
  • Pages are dynamically built (e.g., /songs/[id])

Next.js and Prisma

Before continuing, you'll need to have Node.js (version 10 or higher) installed on your machine.

Download Starter#

To save you time, I've already configured the Prisma schema and database we'll need for this tutorial. Don't worry, I'll explain them! First, clone the repository and install the dependencies.

$ git clone https://github.com/leerob/next-prisma-starter.git
$ cd next-prisma-starter
$ yarn

This includes a few important files.

  • prisma/schema.prisma: Prisma schema file defining your models
  • prisma/dev.db: A SQLite database file containing song/artist data

Prisma Schema & Database#

Let's take a look at our database schema.

We have two models: Song and Artist. A Song includes some necessary information like the name as well as a relation to an Artist. For the sake of this example, a Song can only have one Artist.

prisma/schema.prisma
datasource db {
  provider = "sqlite"
  url      = "file:dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model Song {
  id            Int     @default(autoincrement()) @id
  name          String
  artist        Artist? @relation(fields: [artistId], references: [id])
  artistId      Int?
}

model Artist {
  id    Int     @default(autoincrement()) @id
  name  String  @unique
  songs Song[]
}

I've pre-populated the dev.db with some songs and artists.

idnameartistartistId
NJChelsea Cutler[Artist]1
Take It EasySurfaces[Artist]2
always, i'll careJeremy Zucker[Artist]3
HeartlessThe Weeknd[Artist]4
Daphne BlueThe Band CAMINO[Artist]5

Querying Data#

We have a database and some sample data. Let's build a landing page to display all of the songs. Navigate to pages/index.js. This is the entry point of your application.

This file sets up the boilerplate for getStaticProps to allow us to query the database for our songs. Then, we iterate over our results and display them as a list.

pages/index.js
export async function getStaticProps() {
  return {
    props: {
      songs: [
        {
          id: 1,
          name: 'Test Song'
        }
      ]
    }
  };
}

export default ({ songs }) => (
  <ul>
    {songs.map((song) => (
      <li key={song.id}>{song.name}</li>
    ))}
  </ul>
);

Next, let's get some real data on the screen. After adding the Prisma client, we can fetch all songs using findMany. One of the best features of Prisma is how simple it makes working with relations. We can retrieve the corresponding Artist for the Song using include.

pages/index.js
import { PrismaClient } from '@prisma/client';
export async function getStaticProps() {
const prisma = new PrismaClient();
const songs = await prisma.song.findMany({
include: { artist: true }
});
return { props: { songs } }; } export default ({ songs }) => ( <ul> {songs.map((song) => ( <li key={song.id}>{song.name}</li> ))} </ul> );

When Next.js compiles your page, it will query the database and display all the songs as a list. Static site, dynamic data!

Database Migrations & Editing Data#

A list of songs is great, but what if we want more? Let's say we want to include the music video on YouTube for each song. This will require changes to the database. Don't worry – Prisma has you covered 😎

Prisma Migrate is a tool that lets you change your database schema, e.g. by creating new tables or adding columns to existing tables. These changes are called schema migrations.

Prisma Migrate is still experimental, but it will be ready for production soon. In the meantime, you can still perform schema migrations using plain SQL or another migration tool if you choose.

Let's add some new fields to our schema.

prisma/schema.prisma
datasource db {
  provider = "sqlite"
  url      = "file:dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model Song {
  id            Int     @default(autoincrement()) @id
  name          String
youtubeId String?
albumCoverUrl String?
artist Artist? @relation(fields: [artistId], references: [id]) artistId Int? } model Artist { id Int @default(autoincrement()) @id name String @unique songs Song[] }

To create the migration, we can run:

$ npx prisma migrate save --experimental

This creates a new folder prisma/migrations tracking every change you've made. Let's run the migration.

$ npx prisma migrate up --experimental

Finally, we need to update our Prisma Client API (node_modules/@prisma/client) to recognize the new changes.

$ npx prisma generate

Prisma Flow

That's it! You now have two new fields available on the Song model. Now, let's update our database and populate those values. There's a variety of ways to do this:

  1. Good ol' fashioned SQL commands
  2. Run a Node.js script invoking the Prisma client
  3. Use a visual editor to modify the database

I don't know about you, but #3 seems the easiest to me. Luckily, Prisma has another fancy tool called Prisma Studio that allows us to do just that.

$ npx prisma studio --experimental

Prisma Studio

For convenience, I've added these commands as scripts in the package.json already.

package.json
{
  "scripts": {
    "db": "prisma studio --experimental",
    "db-save": "prisma migrate save --experimental",
    "db-up": "prisma migrate up --experimental",
    "generate": "prisma generate"
  }
}

Dynamic Routes#

Now that we know each song's YouTube video, let's create a page for each song. Using Next.js, we can use brackets to denote a dynamic route (e.g., pages/songs/[id].js)

Once again, I've included the page boilerplate to use getStaticPaths and getStaticProps. Try navigating to /songs/1 in your browser. You should see a YouTube video.

pages/songs/[id].js
export async function getStaticProps({ params }) {
  return {
    props: {
      song: {
        youtubeId: 'N6SQ9QoSjCI'
      }
    }
  };
}

export async function getStaticPaths() {
  return {
    paths: [
      {
        params: {
          id: '1'
        }
      }
    ],
    fallback: false
  };
}

export default ({ song }) => (
  <iframe
    width="100%"
    height="315"
    src={`https://www.youtube.com/embed/${song.youtubeId}`}
    frameBorder="0"
    allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
    allowFullScreen
  />
);

Now, let's hook this up to real data.

  • getStaticProps will query song data for the given id
  • getStaticPaths will generate a page for each song in the database
pages/songs/[id].js
import { PrismaClient } from '@prisma/client';
export async function getStaticProps({ params }) {
const prisma = new PrismaClient();
const song = await prisma.song.findOne({
include: { artist: true },
where: {
id: Number(params.id)
}
});
return { props: {
song
} }; } export async function getStaticPaths() {
const prisma = new PrismaClient();
const songs = await prisma.song.findMany();
return {
paths: songs.map((song) => ({
params: {
id: song.id.toString()
}
})), fallback: false }; } export default ({ song }) => ( <iframe width="100%" height="315" src={`https://www.youtube.com/embed/${song.youtubeId}`} frameBorder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen /> );

Try going to /songs/4 – we're now pulling from the database!

Deployment#

Deploying static sites couldn't be easier with Vercel. After installing the Vercel CLI, simply run vc in the root of the project.

$ vc

That's it.

Conclusion#

Next.js 9.3 and Prisma are a fantastic combo. The combination of dynamic data and static sites result in an incredible user experience.

To see the completed tutorial code with styles added, check out my next-prisma repo. If you prefer video form, you can watch me create this application during a Prisma Day workshop.

React 2025

Build and deploy a modern Jamstack application using the most popular open-source software.

Discuss on TwitterEdit on GitHub
Spotify album cover

/tools/photos