풀스택 강의 5편. Next.js 서버 렌더링을 이용하여 Cloudflare Pages로 배포하기(D1 DB, Drizzle ORM)

안녕하세요?

최근에 풀스택 강의를 여러 프레임워크로 시도했었는데요.

Next.Js가 빠졌었네요.

그래서 오늘은 Next.js를 이용해서 Cloudflare Pages에 배포까지 해보겠습니다.

전체 강의 리스트입니다.

  1. 풀스택 강의 1편. Cloudflare Pages + Workers + D1 + React로 풀스택 개발하기

  2. 풀스택 강의 2편. Cloudflare Pages 안에서 Workers를 이용한 D1 DB 제어하는 API 만들기

  3. 풀스택 강의 3편. AstroJS와 Cloudflare Pages, D1, Drizzle ORM으로 개발하기

  4. 풀스택 강의 4편. Remix + Cloudflare Pages + D1 DB + Drizzle ORM

  5. 풀스택 강의 5편. Next.js 서버 렌더링을 이용하여 Cloudflare Pages로 배포하기(D1 DB, Drizzle ORM)

  6. 풀스택 강의 6편. Remix로 Github 저장소를 DB로 이용해서 KV와 함께 Cloudflare에 배포하기

  7. 풀스택 강의 7편. Vite React 템플릿을 Hono를 이용하여 풀스택 앱으로 개조하기


** 목 차 **

  1. Next.js 템플릿 설치

  2. Wrangler로 개발 서버 돌리기

  3. D1 DB 세팅

  4. Drizzle Schema 만들기

  5. DB 마이그레이션 파일 만들기

  6. DB에 테이블 만들기

  7. Env 세팅

  8. Next.js에서 DB 불러오기

  9. 개발 서버를 위해 npm scripts 설정하기

  10. ADD Todo 작성하기

  11. POST 엔드포인트 작성하기

  12. DELETE 관련 코드 작성하기

  13. 배포하기


1. Next.js 템플릿 설치

기본적인 템플릿을 아래와 같이 만들었습니다.

npm create next-app@latest nextjs-cloudflare
npm i -D @cloudflare/next-on-pages
npm i wrangler

여기서 @cloudflare/next-on-pages 패키지를 설치했는데요.

이 패키지가 Cloudflare가 Next.js를 다루는 패키지입니다.

기본적으로 Next.js 앱을 빌드하면 '.next' 폴더가 생기는데요.

Cloudflare는 다른 방식으로 Next.js 앱을 처리합니다.

아까 설치한 @cloudflare/next-on-pages 패키지의 CLI 명령어로 빌드시켜야 하는데요.

npx @cloudflare/next-on-pages                                                 
⚡️ @cloudflare/next-on-pages CLI v.1.8.2
⚡️ Detected Package Manager: npm (9.6.7)
⚡️ Preparing project...
⚡️ Project is ready
⚡️ Building project...
▲  Vercel CLI 33.0.1
▲  WARNING: You should not upload the `.next` directory.
▲  Installing dependencies...
▲  up to date in 1s
▲  140 packages are looking for funding
▲  run `npm fund` for details
▲  Detected Next.js version: 14.0.4
▲  Detected `package-lock.json` generated by npm 7+
▲  Running "npm run build"
▲  > nextjs-cloudflare@0.1.0 build
▲  > next build
▲  ▲ Next.js 14.0.4
▲  
▲  Creating an optimized production build ...
▲  ✓ Compiled successfully
▲  Linting and checking validity of types ...
▲  Collecting page data ...
▲  ⚠ Using edge runtime on a page currently disables static generation for that page
▲  Generating static pages (0/5) ...
▲  Generating static pages (1/5)
▲  Generating static pages (2/5)
▲  Generating static pages (3/5)
▲  ✓ Generating static pages (5/5)
▲  Finalizing page optimization ...
▲  Collecting build traces ...
▲  
▲  Route (app)                              Size     First Load JS
▲  ┌ ○ /                                    137 B            82 kB
▲  ├ ○ /_not-found                          875 B          82.7 kB
▲  └ ℇ /api/hello                           0 B                0 B
▲  + First Load JS shared by all            81.8 kB
▲  ├ chunks/938-5e061ba0d46125b1.js       26.7 kB
▲  ├ chunks/fd9d1056-735d320b4b8745cb.js  53.3 kB
▲  ├ chunks/main-app-7818b5e7b9b78894.js  220 B
▲  └ chunks/webpack-cb11c3e0a0873661.js   1.64 kB
▲  ○  (Static)        prerendered as static content
▲  ℇ  (Edge Runtime)  server-rendered on demand using the Edge Runtime
▲  Traced Next.js server files in: 728.941ms
▲  Created all serverless functions in: 1.422s
▲  Collected static files (public/, static/, .next/static): 6.176ms
▲  Build Completed in .vercel/output [31s]
⚡️ Completed `npx vercel build`.

⚡️ Build Summary (@cloudflare/next-on-pages v1.8.2)
⚡️ 
⚡️ Edge Function Routes (1)
⚡️   - /api/hello
⚡️ 
⚡️ Prerendered Routes (3)
⚡️   ┌ /
⚡️   ├ /favicon.ico
⚡️   └ /index.rsc
⚡️ 
⚡️ Other Static Assets (30)
⚡️   ┌ /_app.rsc.json
⚡️   ├ /_document.rsc.json
⚡️   ├ /_error.rsc.json
⚡️   ├ /404.html
⚡️   └ ... 26 more

⚡️ Build log saved to '.vercel/output/static/_worker.js/nop-build-log.json'
⚡️ Generated '.vercel/output/static/_worker.js/index.js'.
⚡️ Build completed in 0.58s

자체적으로 'next build'를 실행시키며 Cloudflare Pages에 맞게 조정해 주고 있습니다.


2. Wrangler로 개발 서버 돌리기

Cloudflare CLI 명령 툴인 Wranlger로 개발 서버를 돌려야 합니다.

단순하게 'npm run dev'로 하시면 Cloudflare가 제공해 주는 Pages와 Function 부분을 이용할 수 없습니다.

그래서 다음과 같이 하시면 됩니다.

npx wrangler pages dev ./.vercel/output/static --compatibility-date=2023-12-26 --compatibility-flag="nodejs_compat"

위 명령어를 보시면 @cloudflare/next-on-pages 패키지가 만든 '.vercel' 폴더가 보이는데요.

이 '.vercel' 폴더에 전체 Next.js 앱이 빌드되어 있습니다.

단순히 빌드된 폴더를 웹서빙해서 개발서버를 돌리는거죠.

그리고 compatibility 관련 옵션이 두개가 있는데요.

마지막 옵션인 '--compatibility-flag=nodejs_compat' 옵션이 아주 치명적입니다.

이게 없으면 개발 서버가 작동 안 하죠.

실제 배포했을 때도 이 compatibility flag 부분을 직접 Cloudflare 대시보드에서 지정해 줘야 합니다.

Next.js와 Cloudflare 간의 호환성 부분을 지정해 주는 거라 보시면 됩니다.


3. D1 DB 세팅

지난 시간부터 계속 이용했던 Drizzle ORM을 이용하겠습니다.

npm i drizzle-orm better-sqlite3
npm i -D @types/better-sqlite3 drizzle-kit

이제 관련 패키지를 설치했으니 Cloudflare 계정에 D1 DB를 생성해야겠죠.

➜  nextjs-cloudflare git:(main) ✗ npx wrangler login

➜  nextjs-cloudflare git:(main) ✗ npx wrangler d1 create nextjs-db
✅ Successfully created DB 'nextjs-db' in region APAC
Created your database using D1's new storage backend. The new storage backend is not yet recommended for production
workloads, but backs up your data via point-in-time restore.

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "nextjs-db"
database_id = "4addda6-2dd1-4107-baaf-4ed4asdf238b"

먼저, 로그인하면 웹브라우저에 Cloudflare로 로그인하라는 화면이 나옵니다.

로그인하고 그다음에 d1 create 하시면 됩니다.

이제 위에 나온 'd1_databases' 관련 내용을 wrangler.toml 파일에 저장하면 됩니다.

name = "nextjs-cloudflare"
compatibility_date = "2023-12-26"

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "nextjs-db"
database_id = "4addda6-2dd1-4107-baaf-4ed4asdf238b"

4. Drizzle Schema 만들기

Todo 앱을 위한 DB 테이블을 만들어야 하는데요.

Next.js 템플릿 만들 때 src 폴더를 선택했다면 src 폴더 밑에 db 폴더를 만들고 그 밑에 'schema.ts' 파일을 아래와 같이 만들면 됩니다.

import { integer, text, sqliteTable } from "drizzle-orm/sqlite-core";

export const todos = sqliteTable("todos", {
  id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
  name: text("name"),
  isCompleted: integer("isCompleted", { mode: "boolean" })
    .notNull()
    .default(false),
});

DB 스키마는 지난 시간 강좌에서 본 그대로입니다.


5. DB 마이그레이션 파일 만들기

Drizzle을 이용해서 DB 스키마를 만들었으면 실제 SQL 문구를 만들어야 하는데요.

drizzle-kit을 이용하면 쉽게 만들 수 있습니다.

npx drizzle-kit generate:sqlite --schema=./src/db/schema.ts
drizzle-kit: v0.20.8
drizzle-orm: v0.29.2

Error: Transform failed with 1 error:
/Users/cpro95/Codings/Javascript/blog/nextjs-cloudflare/src/db/schema.ts:3:7: ERROR: Transforming const to the configured target environment ("es5") is not supported yet

에러가 났습니다.

drizzle 패키지는 'es5'를 지원하지 않는데요.

tsconfig.json 파일에서 compileOptions 부분에서 target 부분을 'es6'로 바꾸면 됩니다.

{
  "compilerOptions": {
    "target": "es6",
  }
}

이제 다시 마이그레이션을 실행해 봅시다.

➜  nextjs-cloudflare git:(main) ✗ npx drizzle-kit generate:sqlite --schema=./src/db/schema.ts
drizzle-kit: v0.20.8
drizzle-orm: v0.29.2

1 tables
todos 3 columns 0 indexes 0 fks

[✓] Your SQL migration file ➜ drizzle/0000_mixed_emma_frost.sql 🚀

위와 같이 drizzle 폴더 밑에 sql 파일이 생성됐습니다.

한번 볼까요?

CREATE TABLE `todos` (
	`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
	`name` text,
	`isCompleted` integer DEFAULT false NOT NULL
);

SQL 문구입니다.

이 문구를 이용해서 테이블을 만들면 됩니다.


6. DB에 테이블 만들기

이제 DB에 실제 테이블을 만들어야 하는데요.

어떻게 만들까요?

아까 Drizzle로 만들었던 마이그레이션 파일을 이용하면 됩니다.

wrangler는 D1 DB 관련 로컬 개발을 위해 miniflare 패키지를 제공해 주는데요.

'--local' 옵션을 주면 로컬에 실제 테스트 DB를 만들어 줍니다.

이제 만들어 볼까요?

➜  nextjs-cloudflare git:(main) ✗ npx wrangler d1 execute nextjs-db --local --file=./drizzle/0000_mixed_emma_frost.sql
🌀 Mapping SQL input into an array of statements
🌀 Executing on local database nextjs-db (4a2bfda6-5291-4807-ba7f-4e742f60238b) from .wrangler/state/v3/d1:

위와 같이 '--local' 옵션으로 실행했습니다.

만약 '--local' 옵션을 지우고 실행하면 실제 Cloudflare 서버에서 실행됩니다.

나중에 최종 개발됐을 때 실제 Cloudflare 서버에도 todos 테이블이 있어야 하니까 이 단계에서 '--local' 옵션을 빼고 실행해 주면 됩니다.

npx wrangler d1 execute nextjs-db --file=./drizzle/0000_mixed_emma_frost.sql

이제 로컬 개발 서버에 자체 DB까지 만들었으니까, 테스트를 위해 더미 데이터를 sql 테이블에 넣어 볼까요?

➜  nextjs-cloudflare git:(main) ✗ npx wrangler d1 execute nextjs-db --local --command="insert into todos (name, isCompleted) values ('test 1', 0);"
🌀 Mapping SQL input into an array of statements
🌀 Executing on local database nextjs-db (4a2bfda6-5291-4807-ba7f-4e742f60238b) from .wrangler/state/v3/d1:

여기서 todos 테이블 스키마에 이상한 부분이 있는데요.

바로 isCompleted가 숫자 0이 지정되었습니다.

분명히 Drizzle에서는 Boolean 타입으로 지정했는데요.

왜냐하면 Sqlite3는 Boolean 타입이 없습니다.

Drizzle이 알아서 0을 false로 1을 true로 변환시켜 줍니다.

그래서 위처럼 직접 sqlite3 명령어로 insert 해야 할 때는 숫자 0을 사용한 겁니다.

잘 들어갔는지 터미널에서 확인해 볼까요?

➜  nextjs-cloudflare git:(main) ✗ npx wrangler d1 execute nextjs-db --local --command="select * from todos"
🌀 Mapping SQL input into an array of statements
🌀 Executing on local database nextjs-db (4a2bfda6-5291-4807-ba7f-4e742f60238b) from .wrangler/state/v3/d1:
┌────┬────────┬─────────────┐
│ id │ name   │ isCompleted │
├────┼────────┼─────────────┤
│ 1  │ test 1 │ 0           │
└────┴────────┴─────────────┘

지금까지 아주 잘되고 있네요.

테스트를 위해 몇 개 더 만들겠습니다.

➜  npx wrangler d1 execute nextjs-db --local --command="insert into todos (name, isCompleted) values ('test 2', 1);"
➜  npx wrangler d1 execute nextjs-db --local --command="insert into todos (name, isCompleted) values ('test 3', 0);"
➜  npx wrangler d1 execute nextjs-db --local --command="insert into todos (name, isCompleted) values ('test 4', 1);"

➜  nextjs-cloudflare git:(main) ✗ npx wrangler d1 execute nextjs-db --local --command="select * from todos"
🌀 Mapping SQL input into an array of statements
🌀 Executing on local database nextjs-db (4a2bfda6-5291-4807-ba7f-4e742f60238b) from .wrangler/state/v3/d1:
┌────┬────────┬─────────────┐
│ id │ name   │ isCompleted │
├────┼────────┼─────────────┤
│ 1  │ test 1 │ 0           │
├────┼────────┼─────────────┤
│ 2  │ test 2 │ 1           │
├────┼────────┼─────────────┤
│ 3  │ test 3 │ 0           │
├────┼────────┼─────────────┤
│ 4  │ test 4 │ 1           │
└────┴────────┴─────────────┘

이제 개발을 위한 준비가 끝났네요.


7. Env 세팅

타입스크립트를 사용하고 있으니까, Cloudflare의 D1 DB에 대한 타입을 지정해 주는 게 좋습니다.

src 폴더에 'env.d.ts' 파일을 아래와 같이 만들어 줍니다.

// /src/env.d.ts

import { D1Database } from "@cloudflare/workers-types";

declare global {
  namespace NodeJS {
    interface ProcessEnv {
      DB: D1Database;
    }
  }
}

사실 이렇게 안 해줘도 되는데요. 나중에 설명하겠습니다.


8. Next.js에서 DB 불러오기

App Router 방식으로 Next.js 템플릿을 설정했기 때문에 layout.tsx 파일과 page.tsx 파일이 있는데요.

layout.tsx 파일은 건드릴 게 없고, page.tsx 파일만 건들면 됩니다.

import { drizzle } from "drizzle-orm/d1";
import { todos } from "@/db/schema";

export const runtime = "edge";

const DB = process.env.DB;

export default async function Home() {
  const db = drizzle(DB);

  const result = await db.select().from(todos).all();
  console.log(result);

  return (
    <main className="flex flex-col items-center justify-between p-24 space-y-10">
      <div>Next.js Cloudflare D1 Drizzle Todos</div>

      <ul>
        {result &&
          result.map((d: any) => (
            <li key={d.id} className="flex justify-start gap-5">
              {d.name} / {d.isCompleted.toString()}
            </li>
          ))}
      </ul>
    </main>
  );
}

위 코드에서 가장 중요한 부분이 바로 runtime 이란 변수를 'edge'로 설정해서 export 해야 하는데요.

이렇게 하면 Cloudflare의 엣지 런타임에서 작동하게 됩니다.

모든 서버 사이드 렌더링 컴포넌트에 위와 같이 runtime이 'edge'인 변수를 export 해야 합니다.

이제 Cloudflare의 D1 DB를 Next.js 코드에서 사용해야 하는데요.

아까 설정했던 wrangler.toml 파일에서 보시면 Cloudflare의 D1 DB 이름이 'DB'잖아요.

그래서 단순하게 process.env.DB라고 가져오고 있습니다.

우리가 아까 전에 'env.d.ts'파일 안에 DB 관련 타입을 설정했기 때문입니다.

만약 'env.d.ts'파일을 안 만들었다면 process.env.DB 관련 아래와 같이 코드를 짜시면 됩니다.

import { D1Database } from '@cloudflare/workers-types';
...
...

export const runtime = 'edge';

const DB = process.env.DB as unknown as D1Database;

위와 같이 하시면 타입스크립트가 오류를 뿜어내지 않고 제대로 작동하게 됩니다.

또, 자세히 보시면 DB 관련 서버사이드 명령어를 그냥 쓰고 있는데요.

바로 Next.js 가 기본적으로 서버 사이드 렌더링이기 때문입니다.

클라이언트 렌더링이라면 useEffect 훅을 이용해서 '/api/get-todos' 로 fetch 해서 데이터를 가져왔겠죠.

당연히 get-todos에 해당하는 API 엔드 포인트로 만들어야 하고요.

개발 서버를 돌려야 하는데요.

여기서 조금 귀찮은 부분이 생깁니다.


9. 개발 서버를 위해 npm scripts 설정하기

좀 더 개발을 쉽게 하기 위해 wrangler 명령어를 npm scripts로 만들겠습니다.

package.json 파일에 아래와 같이 수정하면 됩니다.

"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "pages:build": "npx @cloudflare/next-on-pages",
    "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static",
    "pages:watch": "npx @cloudflare/next-on-pages --watch",
    "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-flag=nodejs_compat"
  },

위에서 보시면 build, deploy, watch, dev 4가지 모드가 있습니다.

언뜻 보시면 터미널 2개 만들어서 한 곳에는 watch, 한 곳에는 dev를 돌리면 될 거 같은데요.

여기서 정말 귀찮은 부분이 있는데요.

Cloudflare가 아직 Next.js를 완벽히 지원해 주는 게 아니라서 watch 부분이 완벽히 핫 리로딩이 되지 않습니다.

그래서 테스트 결과를 보기 위해서는 build하고 dev를 순차적으로 실행시켜야 하는데요.

build 하는 부분이 정말 시간이 오래 걸립니다.

Next.js가 Vercel이라는 회사에서 만들어서 자기네 서버를 사용하는 걸 기본으로 개발하다 보니까 조금 어렵네요.

최근에는 Docker안에서 Next.js 서버를 돌리는 게 나왔다고 하는데요.

그래도 Cloudflare 같은 공짜 Pages+Worker 부분을 사용하고 싶으면 귀찮은 것도 참아야죠.

일단 build 명령 후 dev를 실행시키면 아래와 같이 나옵니다.

브라우저로 들어가기 위해 'b' 버튼을 누르면 아래와 같이 나오고, 터미널 창에서는 console.log 부분이 나올 겁니다.

[
  { id: 1, name: 'test 1', isCompleted: false },
  { id: 2, name: 'test 2', isCompleted: true },
  { id: 3, name: 'test 3', isCompleted: false },
  { id: 4, name: 'test 4', isCompleted: true }
]

Next.js의 서버 사이드 렌더링이 아주 잘 작동하고 있네요.


10. ADD Todo 작성하기

이제 Todo 를 추가하는 코드를 만들어야 하는데요.

지금 Next.js 버전이 '14.0.4'인데요.

당연히 ServerAction을 사용하면 될 줄 알고 여러 번 시도했었는데요.

Cloudflare의 Worker로 변환이 아직 개발되지 않아서 이 방식은 지금 이용할 수 없습니다.

그래서 예전처럼 API 엔드포인트를 만들어야 하는데요.

먼저, UI 부분을 추가하겠습니다.

UI 부분은 클라이언트 렌더링 방식으로 해야 해서 컴포넌트를 따로 만들겠습니다.

src 폴더 밑에 components 폴더를 만들고, 아래와 같이 'NewTodos.tsx' 파일을 만듭니다.

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";

export const NewTodos = () => {
  const [name, setName] = useState<string>("");
  const [isCompleted, setIsCompleted] = useState<boolean>(false);

  const router = useRouter();
  const handleSubmit = async (e: React.SyntheticEvent) => {
    e.preventDefault();
    await fetch(`/api/post-todos`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name,
        isCompleted,
      }),
    });

    router.refresh();
    setName("");
    setIsCompleted(false);
  };
  return (
    <div className="mx-8 mt-4 mb-6">
      <form onSubmit={handleSubmit} className="space-x-2 mb-8">
        <input
          type="text"
          name="title"
          value={name}
          onChange={(e) => setName(e.target.value)}
          placeholder="New todo"
          className="border p-2"
          required
        />
        <input
          type="checkbox"
          name="isCompleted"
          checked={isCompleted}
          onChange={(e) => setIsCompleted(e.target.checked)}
          className="border p-2"
        />

        <button
          type="submit"
          className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
        >
          Add Todo
        </button>
      </form>
    </div>
  );
};

위 코드는 클라이언트 사이드 렌더링이라서 'use client' 디렉티브를 사용했습니다.

그리고 역시나 fetch 부분을 이용해서 post 하고 있는데요.

이제, post 관련 API 엔드포인트를 만들어야 합니다.


11. POST 엔드포인트 작성하기

Next.js에서 엔드포인트는 app 폴더 밑에 api 폴더를 만들고 그 밑에 관련 이름의 폴더를 만들고 최종적으로 route.ts 파일을 만드는 겁니다

fetch('/api/post-todos'.... 이렇게 fetch 하고 있으니까 app/api/post-todos/route.ts 가 되겠네요.

import { drizzle } from "drizzle-orm/d1";
import { todos } from "../../../db/schema";
import { NextResponse } from "next/server";

export const runtime = "edge";
const DB = process.env.DB;

type DataType = {
  name: string;
  isCompleted: boolean;
};
export async function POST(request: Request) {
  const { name, isCompleted } = (await request.json()) as DataType;

  const db = drizzle(DB);
  const result = await db
    .insert(todos)
    .values({
      name,
      isCompleted,
    })
    .run();
  console.log(result);

  return NextResponse.json({ message: "Created Todo" }, { status: 200 });
}

drizzle 부분은 예전 강의를 찾아보시면 됩니다.

이제 테스트해봐야겠네요.

다시 page.tsx 파일에 NewTods 컴포넌트를 추가하고 새로 빌드해 보겠습니다.

import { drizzle } from "drizzle-orm/d1";
import { todos } from "@/db/schema";
import { NewTodos } from "@/components/NewTodos";

export const runtime = "edge";

const DB = process.env.DB;

export default async function Home() {
  const db = drizzle(DB);

  const result = await db.select().from(todos).all();
  console.log(result);

  return (
    <main className="flex flex-col items-center justify-between p-24 space-y-10">
      <div>Next.js Cloudflare D1 Drizzle Todos</div>
      <NewTodos />
      <ul>
        {result &&
          result.map((d: any) => (
            <li key={d.id} className="flex justify-start gap-5">
              {d.name} / {d.isCompleted.toString()}
            </li>
          ))}
      </ul>
    </main>
  );
}

역시나 빌드하고 다시 개발 서버 돌리기가 너무 오래 걸리네요.

정말 Cloudflare에서는 Next.js를 사용하기 싫어지네요.

브라우저에서 보시면 아래와 같이 나오고 테스트를 위해 'tttttttt'를 입력하고 버튼을 누르면 작동이 아주 잘 되는 걸 볼 수 있을 겁니다.


12. DELETE 관련 코드 작성하기

DELETE 코드를 위해 DeleteTodo.tsx 컴포넌트를 아래와 같이 만들면 됩니다.

"use client";

import { useRouter } from "next/navigation";

export const DeleteTodo = ({ todoId }: { todoId: any }) => {
  const router = useRouter();
  const handleClick = async (e: React.SyntheticEvent) => {
    e.preventDefault();
    await fetch(`/api/delete-todos`, {
      method: "delete",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ id: todoId }),
    });

    router.refresh();
  };
  return (
    <button
      type="button"
      onClick={handleClick}
      className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
    >
      Delete
    </button>
  );
};

그리고 delete-todos 엔드포인트는 아래와 같이 'src/app/api/delete-todos/route.ts'파일에 작성하시면 됩니다.

import { drizzle } from "drizzle-orm/d1";
import { todos } from "../../../db/schema";
import { NextResponse } from "next/server";
import { eq } from "drizzle-orm";

export const runtime = "edge";
const DB = process.env.DB;

type DataType = {
  id: string;
};
export async function DELETE(request: Request) {
  const { id } = (await request.json()) as DataType;

  const db = drizzle(DB);
  const result = await db
    .delete(todos)
    .where(eq(todos.id, Number(id)))
    .run();
  console.log(result);

  return NextResponse.json({ message: "Deleted Todo" }, { status: 200 });
}

이제 page.tsx 파일 부분에 DeleteTodo 컴포넌트만 id를 연계해서 추가하면 됩니다.

import { drizzle } from "drizzle-orm/d1";
import { todos } from "@/db/schema";
import { NewTodos } from "@/components/NewTodos";
import { DeleteTodo } from "@/components/DeleteTodo";

export const runtime = "edge";

const DB = process.env.DB;

export default async function Home() {
  const db = drizzle(DB);

  const result = await db.select().from(todos).all();
  console.log(result);

  return (
    <main className="flex flex-col items-center justify-between p-24 space-y-10">
      <div>Next.js Cloudflare D1 Drizzle Todos</div>
      <NewTodos />
      <ul>
        {result &&
          result.map((d: any) => (
            <li key={d.id} className="flex justify-start gap-5">
              {d.name} / {d.isCompleted.toString()}
              <DeleteTodo todoId={d.id} />
            </li>
          ))}
      </ul>
    </main>
  );
}

이제 다시 빌드 후 dev 개발 서버를 돌려보면 아래와 같이 잘 작동되는 게 보일 겁니다.

위 그림과 같이 'test 1' 부분을 삭제한 후의 스크린샷입니다.

제대로 작동하네요.


13. 배포하기

이제 모든 개발이 끝났으니까 Cloudflare Pages로 배포해야 하는데요.

여기서 조금 귀찮은 부분이 있는데요.

Github Action으로 자동 배포하는 방식이 아닌 Wrangler를 사용한 터미널 커맨드 방식이라 처음 배포 명령어를 한 번하고 나서 Cloudflare 대시보드에서 세팅 부분을 바꾼 다음 다시 배포해야 합니다.

일단 아래와 같이 배포합시다.

npm run pages:deploy

위와 같이 하면 build후에 배포하게 됩니다.

```bash
No project selected. Would you like to create one or use an existing project?
❯ Create a new project
  Use an existing project
✔ Enter the name of your new project: … nextjs-todos
✔ Enter the production branch name: … main
✨ Successfully created the 'nextjs-todos' project.

위와 같이 처음 배포하는 거라 프로젝트 이름과 branch name을 물어봅니다.

적당하게 위와 같이 하시면 됩니다.

배포가 되면 Cloudflare 대시보드에 nextjs-todos 라는 Pages라는게 생기는데요.

배포된 주소로 가보면 아래와 같은 에러가 나올 겁니다.

뭔가 익숙한 에러죠.

바로 'nodejs_compat'관련 세팅을 해줘야 합니다.

위와 같이 대시보드에서 Settings - Functions 부분으로 들어가시면 compatibility flag 부분이 보일 겁니다.

production과 preview 두 개다 지정하시면 됩니다.

이제 D1 DB 부분도 대시보드에서 바인딩해줘야 합니다.

화면을 조금 내려보시면 아래와 같이 D1 database bindings 부분이 나오는데요.

아래 그림과 같이 하시면 됩니다.

이제 모든 준비가 끝났습니다.

참고로 아까 로컬 DB 만들 때 원격 DB까지 만들어 둔 상태라서 대시보드에 가보시면 아래처럼 todos 테이블이 있을 겁니다.

이제 다시 'npm run pages:deploy' 명령어로 재 배포 해봅시다.

위와 같이 사이트가 정상 작동하게 될 겁니다.


지금까지 Next.js를 이용해서 Cloudflare Pages에 배포하는 풀스택 강의했는데요.

개발 서버가 핫 리로딩이 안 되니까 개발 시간이 너무 오래 걸립니다.

그래서 아직은 Next.js와 Cloudflare Pages에는 단순하게 정적 사이트만 하시는 걸 추천 드립니다.

그럼.