Next.js Supabase 연결 방법: App Router 설정과 데이터 조회

··설명 약 13분·작성: 해비·블로그 소개

Next.js App Router에 Supabase를 연결하고 실제 테이블 조회를 어떻게 확인하나요?

Next.js에 Supabase를 처음 연결할 때는 URL·공개 키를 설정한 뒤 작은 학습용 테이블을 실제로 읽어 연결을 확인합니다. SDK 설치 성공만으로 DB 접근이 되는 것은 아닙니다.

적용 조건·범위: 검증 범위: 실제 Supabase 요청, 계정 생성, 메일 발송, 원격 DB 정책·실행 계획 검증은 이 편집에서 실행하지 않았습니다. 위 결과는 독자가 확인할 기대값입니다.

본문 근거: supabase.com/docs/guides/getting-started/quickstarts/nextjs · supabase.com/docs/guides/getting-started/api-keys · supabase.com/docs/guides/database/postgres/row-level-security

Supabase 프로젝트 만들기

Supabase Dashboard에서 개발용 프로젝트를 만듭니다.

프로젝트가 준비되면 Connect 화면에서 다음 값을 확인합니다.

Project URL
Publishable key

2026년 9월 공식 문서 기준 publishable key는 브라우저, 모바일 앱, 공개 소스처럼 키를 숨길 수 없는 환경에서 사용하도록 설계되어 있습니다.

반면 secret key는 서버 전용 고권한 키입니다.

secret key는 service_role 권한을 사용하며 RLS를 우회하므로 클라이언트 번들에 포함하면 안 됩니다.

SDK 설치

별도 Next.js App Router 연습 프로젝트에서 설치합니다.

npm install @supabase/supabase-js

이번 회차는 인증 세션이 필요하지 않으므로 @supabase/ssr은 아직 설치하지 않습니다.

SSR 쿠키 인증은 8회차에서 별도로 다룹니다.

환경 변수 연결

프로젝트 루트의 .env.local에 다음 값을 넣습니다.

NEXT_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_REPLACE_ME

위의 YOUR_PROJECT_REF와 sb_publishable_REPLACE_ME는 학습용 자리표시자입니다. 실행할 때 본인의 개발용 프로젝트 URL과 publishable key로 바꾸고, secret key는 넣지 않습니다. 실제 설정 파일은 Git 저장소에 올리지 않습니다.

공유용 파일에는 변수 이름만 남깁니다.

# .env.example
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=

NEXT_PUBLIC_이라는 이름 때문에 publishable key가 위험해지는 것은 아닙니다.

공개 키는 원래 공개 환경에서 사용하는 키입니다.

대신 데이터베이스가 공개 키 요청을 받아도 필요한 행만 내주도록 GRANT와 RLS를 설계해야 합니다.

공개 실습 테이블 만들기

이번 회차에서는 실제 개인 작업 테이블을 만들지 않습니다.

연결 확인용으로 public_tasks_lab를 사용합니다.

Supabase SQL Editor에서 다음 SQL을 실행합니다.

begin;

create table public.public_tasks_lab (
  id bigint generated always as identity primary key,
  title text not null,
  created_at timestamptz not null default now()
);

insert into public.public_tasks_lab (title)
values
  ('Supabase 연결 확인'),
  ('RLS 공개 읽기 확인');

alter table public.public_tasks_lab
enable row level security;

revoke all
on table public.public_tasks_lab
from public, anon, authenticated;

grant select
on table public.public_tasks_lab
to anon, authenticated;

create policy "public_tasks_lab_read"
on public.public_tasks_lab
for select
to anon, authenticated
using (true);

commit;

실제 프로젝트에 같은 이름이 이미 있다면 덮어쓰지 않습니다.

이 글의 SQL에는 drop table을 넣지 않습니다.

GRANT와 RLS는 역할이 다릅니다

Supabase 공식 문서의 현재 설명은 두 단계를 분리합니다.

GRANT
→ 이 역할이 SELECT 같은 작업 자체를 실행할 수 있는가

RLS Policy
→ 그 작업을 어떤 행에 적용할 수 있는가

따라서 SELECT policy를 만들었다고 SELECT 권한 자체가 자동으로 생기는 것은 아닙니다.

반대로 SELECT GRANT만 있고 RLS가 열려 있으면 의도하지 않은 행이 노출될 수 있습니다.

이번 예제는 공개 학습 데이터이므로 anon과 authenticated에 SELECT만 열고, INSERT·UPDATE·DELETE는 열지 않습니다.

서버 전용 공개 조회 클라이언트

lib/supabase/public-server.ts를 만듭니다.

import 'server-only';

import { createClient } from '@supabase/supabase-js';

export function createPublicSupabaseClient() {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const key = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY;

  if (!url || !key) {
    throw new Error('Supabase 공개 설정이 필요합니다.');
  }

  return createClient(url, key, {
    auth: {
      persistSession: false,
      autoRefreshToken: false,
      detectSessionInUrl: false,
    },
  });
}

로그인 세션을 사용하지 않으므로 auth 저장과 자동 갱신을 끕니다.

서버에서 실행한다고 해서 publishable key의 권한이 secret key처럼 올라가지 않습니다.

이 요청은 로그인 정보가 없으므로 anon 역할을 사용합니다.

Server Component에서 데이터 조회

app/tasks/page.tsx에서 목록을 조회합니다.

import { createPublicSupabaseClient } from '@/lib/supabase/public-server';

type PublicTask = {
  id: number;
  title: string;
  created_at: string;
};

export const dynamic = 'force-dynamic';

export default async function TasksPage() {
  const supabase = createPublicSupabaseClient();

  const { data, error } = await supabase
    .from('public_tasks_lab')
    .select('id, title, created_at')
    .order('id');

  if (error) {
    return (
      <main>
        <h1>TaskFlow</h1>
        <p>Supabase 데이터를 불러오지 못했습니다.</p>
        <pre>{error.message}</pre>
      </main>
    );
  }

  const tasks = (data ?? []) as PublicTask[];

  return (
    <main>
      <h1>TaskFlow</h1>

      {tasks.length === 0 ? (
        <p>공개 작업이 없습니다.</p>
      ) : (
        <ul>
          {tasks.map((task) => (
            <li key={task.id}>
              {task.title}
            </li>
          ))}
        </ul>
      )}
    </main>
  );
}

이번 예제에서는 force-dynamic을 사용해 요청 시 조회가 실행되는 것을 쉽게 확인합니다.

모든 Supabase 페이지에 무조건 force-dynamic을 넣어야 한다는 뜻은 아닙니다.

결과를 네 가지 상태로 구분하기

정상 목록

두 개의 샘플 행이 목록에 보입니다.

Supabase 연결 확인
RLS 공개 읽기 확인

정상 빈 목록

테이블이 비어 있다면 data는 빈 배열일 수 있습니다.

연결 실패가 아닙니다.

따라서 빈 목록 UI와 오류 UI를 분리합니다.

권한 오류

SELECT GRANT를 제거하면 permission denied 계열 오류를 확인할 수 있습니다.

RLS policy를 제거하고 RLS를 활성화한 상태라면 publishable key 요청에서 접근 가능한 행이 없어집니다.

GRANT 오류와 policy 결과를 같은 문제로 취급하지 않습니다.

설정 오류

Project URL이나 key가 없으면 앱 내부에서 명시적으로 오류를 발생시킵니다.

.env.local을 수정했다면 개발 서버를 다시 시작합니다.

자주 발생하는 오류

secret key를 NEXT_PUBLIC_에 넣기

하지 않습니다.

NEXT_PUBLIC_SUPABASE_SECRET_KEY

같은 형태의 변수도 만들지 않습니다.

public_tasks_lab를 이후 실제 tasks로 그대로 사용하기

이 테이블은 공개 읽기 실습용입니다.

5회차에서는 별도 migration으로 profiles와 tasks를 새로 만듭니다.

빈 배열을 연결 실패로 처리하기

오류가 null이고 data가 []라면 정상 요청일 수 있습니다.

UI에서도 빈 상태와 오류를 나눕니다.

이 기술만 따로 연습하기

이 글의 완성 실습 ZIP을 별도 폴더에 풀어 단독 샘플로 사용합니다. 이전 회차의 작업 폴더는 필요하지 않습니다. Node.js 24 계열과 ZIP의 package-lock.json을 기준으로 README의 설치·환경 변수 절차를 한 번 진행합니다. 필요한 개발용 Supabase 프로젝트와 공개 가능한 URL·publishable key는 독자가 준비합니다. 실습은 운영 데이터와 분리합니다. 전체 파일은 아래 코드 보기에서도 확인할 수 있습니다.

  1. README에 있는 public_tasks_lab 학습 SQL만 개발 DB에 적용합니다. 이 테이블은 개인 정보가 없는 공개 읽기 샘플입니다.
  2. npm run dev 후 /tasks를 열어 SQL에 들어 있는 제목과 화면 제목을 비교합니다.
  3. app/tasks/page.tsx의 기존 SELECT에서 await 이전 쿼리 체인에 .eq('title', '__없는_실습_제목__')를 추가하고 다시 엽니다. 기존 테이블 행을 삭제하지 않고 빈 결과를 만드는 실험입니다.
  4. 추가한 필터를 제거하고 같은 샘플 제목이 다시 나오는지 확인합니다.

기대 결과: 기본 쿼리는 샘플 목록, 일치하지 않는 제목 필터는 빈 상태를 보여야 합니다. error 없이 []가 반환되는 상황은 연결 실패와 구별됩니다.

자가 확인·변형: 같은 필터에 실제 샘플 제목을 넣으면 무엇이 바뀔까요? 하나 이상의 일치 행이 나오며, GRANT·RLS 오류 화면과는 다른 결과입니다.

검증 범위: 실제 Supabase 요청, 계정 생성, 메일 발송, 원격 DB 정책·실행 계획 검증은 이 편집에서 실행하지 않았습니다. 위 결과는 독자가 확인할 기대값입니다.

학습 완료 체크리스트

  • Project URL과 publishable key의 역할을 설명할 수 있습니다.
  • secret key를 브라우저에 넣으면 안 되는 이유를 알고 있습니다.
  • GRANT와 RLS의 차이를 설명할 수 있습니다.
  • Server Component에서 Supabase 데이터를 조회할 수 있습니다.
  • 빈 목록과 권한 오류를 구분할 수 있습니다.
  • public_tasks_lab가 임시 학습 테이블이라는 것을 이해했습니다.

선택 실습: 할 일 앱에 적용하기

TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 데이터 저장·인증·접근 권한을 연결합니다.

이번 주제: Next.js Supabase 연결 방법: App Router 설정과 데이터 조회.

선택: 연재 프로젝트와 전체 자료

연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.

TaskFlow 프로젝트 적용

이전 1회차 ZIP은 결정 문서만 포함하므로 실행 앱의 시작본으로 사용할 수 없습니다. 기존 Next.js 앱에 .env.example, public-server.ts, app/tasks/page.tsx와 학습 SQL을 적용하거나 이 글의 완성 ZIP을 별도 앱으로 실행하세요. 앱 전체를 이전 Next.js 프로젝트에 덮어쓰지 않습니다.

이전 체크포인트

1회차를 따라왔다면:

supabase-step-01-backend-choice

1회차를 건너뛰었다면:

next-step-38-cache-update

현재 파일 구조

taskflow/
├─ app/
│  └─ tasks/
│     └─ page.tsx
├─ docs/
│  └─ backend-choice.md
├─ lib/
│  └─ supabase/
│     └─ public-server.ts
├─ supabase/
│  └─ learning/
│     └─ 02_public_tasks_lab.sql
├─ .env.example
└─ package.json

이번 변경

Next.js TaskFlow
↓
Supabase publishable key
↓
Data API
↓
GRANT SELECT
↓
RLS public policy
↓
public_tasks_lab

아직 사용자 인증과 개인 데이터는 연결하지 않습니다.

전체 파일·코드 보기

실습 프로젝트 전체 코드

아래는 이 회차까지의 코드를 누적한 독립 실행용 최소 Next.js App Router 학습 앱 전체입니다. 기존 Next.js 38편 앱 전체를 복제한 것이 아닙니다. ZIP에는 의존성 잠금 파일 package-lock.json도 포함되며 본문에서는 이 자동 생성 잠금 파일만 생략합니다. 기존 TaskFlow 앱을 이어서 사용한다면 기존 설정을 보존하고 해당 회차 변경 파일을 적용하세요.

.env.example

NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=

.gitignore

node_modules/
.next/
.env.local
*.tsbuildinfo

app/layout.tsx

import type { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) { return <html lang="ko"><body>{children}</body></html>; }

app/page.tsx

import Link from "next/link";
export default function Home() { return <main><h1>TaskFlow Supabase</h1><Link href="/tasks">작업 목록</Link> <Link href="/login">로그인</Link></main>; }

app/tasks/page.tsx

import { createPublicSupabaseClient } from '@/lib/supabase/public-server';


type PublicTask = {
  id: number;
  title: string;
  created_at: string;
};


export const dynamic = 'force-dynamic';


export default async function TasksPage() {
  const supabase = createPublicSupabaseClient();


  const { data, error } = await supabase
    .from('public_tasks_lab')
    .select('id, title, created_at')
    .order('id');


  if (error) {
    return (
      <main>
        <h1>TaskFlow</h1>
        <p>Supabase 데이터를 불러오지 못했습니다.</p>
        <pre>{error.message}</pre>
      </main>
    );
  }


  const tasks = (data ?? []) as PublicTask[];


  return (
    <main>
      <h1>TaskFlow</h1>


      {tasks.length === 0 ? (
        <p>공개 작업이 없습니다.</p>
      ) : (
        <ul>
          {tasks.map((task) => (
            <li key={task.id}>{task.title}</li>
          ))}
        </ul>
      )}
    </main>
  );
}

docs/backend-choice.md

# TaskFlow Backend Choice


## 시작점


next-step-38-cache-update


## 선택


Supabase


## 이유


- PostgreSQL 관계형 데이터 모델을 직접 학습한다.
- Supabase Auth와 Postgres 사용자를 연결한다.
- GRANT와 RLS를 이용해 사용자별 데이터 격리를 학습한다.
- Next.js App Router에서 쿠키 기반 SSR 인증을 연결한다.
- 관계 조회와 인덱스까지 하나의 데이터 계층에서 학습한다.


## 이번 과정에서 사용하는 기능


- PostgreSQL
- Auth
- Data API
- Row Level Security
- @supabase/supabase-js
- @supabase/ssr


## 이번 과정에서 필수로 다루지 않는 기능


- Realtime
- Storage
- Edge Functions


필요 기능이 아니므로 과정 범위를 무리하게 늘리지 않는다.


## 보안 원칙


- 브라우저에는 publishable key만 사용한다.
- secret key는 클라이언트에 포함하지 않는다.
- 사용자 데이터는 RLS를 활성화한다.
- 화면에서 숨기는 것으로 권한 검증을 대신하지 않는다.
- 실제 인증 토큰과 개인정보를 학습 자료에 저장하지 않는다.


## 백엔드 트랙


Firebase 완료본을 이어서 수정하지 않는다.


Next.js TaskFlow 최종 체크포인트에서 별도 Supabase 브랜치를 시작한다.


## 다음 단계


Supabase 프로젝트를 만들고 공개 학습 데이터를 한 번 조회한다.

lib/supabase/public-server.ts

import 'server-only';


import { createClient } from '@supabase/supabase-js';


export function createPublicSupabaseClient() {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const key = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY;


  if (!url || !key) {
    throw new Error('Supabase 공개 설정이 필요합니다.');
  }


  return createClient(url, key, {
    auth: {
      persistSession: false,
      autoRefreshToken: false,
      detectSessionInUrl: false,
    },
  });
}

next-env.d.ts

/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

package.json

{
  "name": "taskflow-supabase-checkpoint",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "next": "16.3.5",
    "react": "19.3.0",
    "react-dom": "19.3.0",
    "@supabase/supabase-js": "2.116.0",
    "server-only": "0.0.1"
  },
  "devDependencies": {
    "typescript": "5.9.3",
    "@types/node": "24.10.1",
    "@types/react": "19.2.7",
    "@types/react-dom": "19.2.3"
  }
}

README.md

# Supabase 02 완료 체크포인트 — first-query

원문 회차 코드를 누적한 독립 실행용 최소 Next.js App Router 학습 앱입니다. 이전 Next.js 38편 앱 전체나 추가 CRUD 화면을 복제한 프로젝트가 아닙니다.

Node.js 24.18.0, npm 11.16.0 기준입니다.

1. `npm ci`
2. `.env.example`을 `.env.local`로 복사하고 개발 전용 Supabase URL/publishable key를 입력합니다.
3. 아래 순서대로 개발 DB에 해당 SQL을 한 번씩 적용합니다. 같은 객체가 있으면 덮어쓰지 않습니다.
4. `npm run dev` 후 `/tasks`를 확인합니다.
5. `npm run typecheck` 및 `npm run build`를 실행합니다.

SQL: `supabase/learning/02_public_tasks_lab.sql`.


실제 secret/service_role 키, 비밀번호, 원격 DB 계정은 포함하지 않습니다. node_modules/.next/.env.local은 제외했습니다. ZIP의 package-lock.json으로 정확한 의존성을 재현하세요.

supabase/learning/02_public_tasks_lab.sql

begin;


create table public.public_tasks_lab (
  id bigint generated always as identity primary key,
  title text not null,
  created_at timestamptz not null default now()
);


insert into public.public_tasks_lab (title)
values
  ('Supabase 연결 확인'),
  ('RLS 공개 읽기 확인');


alter table public.public_tasks_lab
enable row level security;


revoke all
on table public.public_tasks_lab
from public, anon, authenticated;


grant select
on table public.public_tasks_lab
to anon, authenticated;


create policy "public_tasks_lab_read"
on public.public_tasks_lab
for select
to anon, authenticated
using (true);


commit;

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2017",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": [
        "./*"
      ]
    },
    "allowJs": true
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    ".next/dev/types/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

실행·결과 확인

npm run dev

브라우저에서 /tasks를 엽니다.

확인할 내용:

  • 샘플 2개가 표시되는가
  • 일치하지 않는 제목 필터로 조회하면 샘플 행을 그대로 두고 빈 상태가 나오는가
  • 환경 변수를 제거하면 설정 오류가 나는가
  • SELECT GRANT와 RLS가 요청 결과에 영향을 주는가

이 문서는 코드와 공식 API 구조를 기준으로 작성했습니다.

실제 Supabase 원격 프로젝트의 결과는 독자의 프로젝트 URL·키·SQL 적용 상태에 따라 달라지므로, 원격 실행 여부와 로컬 Next.js build 여부를 별도로 기록합니다.

완료 체크포인트

supabase-step-02-first-query

프로젝트 적용 후 확인 기록

독자 질문: 첫 SELECT가 성공한 것과 테이블이 비어 있거나 권한으로 차단된 것을 어떻게 구별할까?

따라 할 단계

  1. 학습용 public_tasks_lab SQL을 적용하고 anon SELECT GRANT와 공개 읽기 policy를 확인한다.
  2. 환경 변수를 설정해 개발 서버를 재시작한 뒤 /tasks를 열고 샘플 행을 확인한다.
  3. 상단의 불일치 제목 필터로 빈 화면을 확인하고, 별도 실습 환경에서 SELECT GRANT를 제거했다가 원복해 오류와 비교한다.

예상 결과와 A/B 계정 범위

  • 샘플 행이 있으면 제목 목록, 행이 없으면 빈 상태, GRANT가 빠지면 권한 오류가 기대된다. policy만 불일치하면 에러 없이 빈 배열일 수도 있다.
  • 이 회차는 로그인 전 공개 테이블만 다룬다. A/B 사용자별 허용·거부는 5~7회차에서 확인한다.

흔한 오류 점검

  • []와 error가 null이면 연결 실패로 단정하지 않고 테이블 행·policy를 확인한다.
  • 환경 변수 변경이 반영되지 않으면 개발 서버를 재시작하고 Project URL·publishable key의 프로젝트 일치를 확인한다.

검증 기록: 이번 편집에서 실제 Supabase 요청·A/B 계정 실험은 실행하지 않았으므로 미확인입니다.

다음 학습

3회차에서 A와 B를 각각 가입·로그인시켜 세션 전환을 확인한다.

다음 학습

다음 글에서는 이메일과 비밀번호로 회원가입·로그인·로그아웃을 구현하고, publishable key를 쓰는 같은 앱에서도 로그인 전 anon과 로그인 후 authenticated 역할이 달라지는 흐름을 확인합니다.

다음 글:

Supabase Auth 이메일·비밀번호 회원가입·로그인·세션 기초

공식 문서

확인일: 2026-09-21

체크포인트 다운로드

supabase-step-02-first-query 완료 ZIP 다운로드

시작 체크포인트: 1회차 완료 ZIP

글 소개·학습 준비·전체 목차
브라우저와 데이터베이스를 연결하는 Supabase 첫 연결 표지

Supabase · 2 / 9 · 초급 학습 · 첫 연결

Supabase를 처음 사용할 때 가장 먼저 확인해야 할 것은 “SDK가 설치됐는가”보다 실제 요청이 어떤 권한으로 데이터베이스까지 도달하는가입니다.

이번 글에서는 Next.js App Router 프로젝트에 Supabase JavaScript SDK를 설치하고, Project URL과 publishable key를 연결한 뒤, 로그인 없이 읽을 수 있는 작은 학습용 테이블을 Server Component에서 조회합니다.

이 단계에서 사용하는 public_tasks_lab는 연결과 공개 SELECT만 확인하기 위한 폐기 가능한 실습 테이블입니다.

5회차에서 만들 실제 TaskFlow 데이터 모델인 profiles / tasks와는 별개입니다.

이번 글에서 배우는 내용

  • Supabase 프로젝트 생성
  • @supabase/supabase-js 설치
  • Project URL과 publishable key 연결
  • publishable key와 secret key 차이
  • Server Component에서 공개 데이터 조회
  • GRANT와 RLS가 함께 필요한 이유
  • 정상 목록·빈 목록·권한 오류·설정 오류 구분
  • 공개 실습 테이블과 실제 사용자 테이블을 분리하는 이유

선수 지식

  • Next.js App Router 기본 구조
  • TypeScript 기본
  • async / await
  • 터미널에서 npm 명령 실행

1회차를 건너뛴 경우에도 진행할 수 있습니다.

단독 실습은 이 글의 완성 샘플 ZIP으로 시작합니다. Next.js TaskFlow를 이어 만드는 경로와 시작본은 하단에 따로 안내합니다.

목차

  1. Supabase 프로젝트 만들기
  2. SDK 설치
  3. 환경 변수 연결
  4. 공개 실습 테이블 만들기
  5. GRANT와 RLS는 역할이 다릅니다
  6. 서버 전용 공개 조회 클라이언트
  7. Server Component에서 데이터 조회
  8. 결과를 네 가지 상태로 구분하기
  9. 자주 발생하는 오류
  10. 학습 완료 체크리스트
  11. TaskFlow 프로젝트 적용
  12. 실습 프로젝트 전체 코드
  13. 실행·결과 확인
  14. 다음 학습
  15. 공식 문서
  16. 단독 실습과 결과 확인

이 글이 도움이 되었나요?

조회 중

분류별 과정 이동 (선택)

읽음 기록

읽음 — / 전체 9개 · 필수 8개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기