테스트 사이트 - 개발 중인 베타 버전입니다

Next.js App Rules (Supabase + ShadCN + TailwindCSS)

· 4개월 전 · 188 · 1

# Next.js App Rules (Supabase + ShadCN + TailwindCSS)

 

## Next.js: Route Handler 우선 사용

 

-   모든 API 엔드포인트는 Route Handler를 사용하여 구현하세요.

-   데이터베이스 작업, 외부 API 호출, 인증 등 복잡한 서버 작업은 반드시 Route Handler를 사용하세요.

-   Server Action은 단순 폼 제출 또는 간단한 데이터 처리에만 사용하세요.

 

---

 

## Next.js 라우팅: App Router 사용

 

-   프로젝트 내 라우팅은 Pages Router 대신 App Router를 사용하세요.

 

---

 

## 가급적 서버 컴포넌트 사용

 

- 프로젝트 내 가급적 서버 컴포넌트 사용하세요.

- 필요에 서버 컴포넌트에서 할 수 없는 경우 컴포넌트를 분리 하여 사용하세요

 

## 코드 긴거 방지

 

- 한 페이지 내 코드가 길어짐을 방지하기 위하여, 가급적 컴포넌트를 분리하여 유지 보수 및 재활용 가치를 높이도록 사용하세요.

 

## TypeScript 사용: TS 사용 권장

 

-   프로젝트 전반에 TypeScript를 사용하세요.

-   타입 안정성을 위해 모든 컴포넌트와 서버 로직에 TypeScript를 적용하세요.

 

---

 

## TypeScript 인터페이스 정의 규칙: 'I' 접두사 사용

 

-   인터페이스 정의 시 이름 앞에 'I'를 접두사로 추가하세요.

-   예시:

    ```typescript

    export interface IComment {

        id: string

        text: string

        author: string

    }

   

    export interface IUser {

        id: string

        email: string

        name: string

        created_at: string

        updated_at: string

    }

    ```

-   인터페이스 생성은 types/index.ts 파일에 작성하세요.

 

---

 

## 컴포넌트 생성: ShadCN 우선 사용

 

-   모든 UI 컴포넌트는 ShadCN을 우선으로 생성하세요.

-   ShadCN 컴포넌트 생성 CLI 명령어는 `npx shadcn@latest add`입니다.

-   Toast 관련 컴포넌트는 다음 위치에 있습니다:

    ```

    components/ui/toast.tsx      # Toast 기본 컴포넌트

    components/ui/toaster.tsx    # Toast 컨테이너 컴포넌트

    hooks/use-toast.ts   # Toast 커스텀 훅

    ```

 

---

 

## 스타일링: TailwindCSS 사용

 

-   모든 스타일링은 TailwindCSS를 사용하세요.

-   ShadCN과 TailwindCSS의 호환성을 위해 기본 설정을 유지하세요.

-   CSS 모듈이나 styled-components는 사용하지 않습니다.

-   커스텀 컴포넌트 생성 시 TailwindCSS 클래스를 사용하세요.

 

---

 

## 데이터베이스: Supabase 사용

 

-   모든 데이터베이스 작업은 Supabase를 사용하세요.

-   Supabase Client 설정:

    ```typescript

    // lib/supabase.ts

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

   

    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!

    const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

   

    export const supabase = createClient(supabaseUrl, supabaseKey)

    ```

 

-   서버 컴포넌트에서 Supabase 사용:

    ```typescript

    // utils/supabase/server.ts

    import { createServerClient } from '@supabase/ssr'

    import { cookies } from 'next/headers'

   

    export const createClient = async () => {

        const cookieStore = await cookies()

       

        return createServerClient(

            process.env.NEXT_PUBLIC_SUPABASE_URL!,

            process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,

            {

                cookies: {

                    getAll() {

                        return cookieStore.getAll()

                    },

                    setAll(cookiesToSet) {

                        try {

                            cookiesToSet.forEach(({ name, value, options }) => {

                                cookieStore.set(name, value, options)

                            })

                        } catch (error) {

                            // SSR에서는 쿠키 설정이 제한될 수 있습니다

                        }

                    },

                }

            }

        )

    }

    ```

 

---

 

## 인증: Supabase Auth 사용

 

-   모든 인증은 Supabase Auth를 사용하세요.

-   미들웨어 설정:

    ```typescript

    // middleware.ts

    import { createServerClient } from '@supabase/ssr'

    import { NextResponse, type NextRequest } from 'next/server'

   

    export async function middleware(request: NextRequest) {

        let supabaseResponse = NextResponse.next({

            request,

        })

   

        const supabase = createServerClient(

            process.env.NEXT_PUBLIC_SUPABASE_URL!,

            process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,

            {

                cookies: {

                    getAll() {

                        return request.cookies.getAll()

                    },

                    setAll(cookiesToSet) {

                        cookiesToSet.forEach(({ name, value, options }) => {

                            request.cookies.set(name, value)

                            supabaseResponse.cookies.set(name, value, options)

                        })

                    },

                }

            }

        )

   

        // 사용자 인증 확인

        const { data: { user } } = await supabase.auth.getUser()

       

        // 보호된 라우트 처리

        if (!user && request.nextUrl.pathname.startsWith('/protected')) {

            return NextResponse.redirect(new URL('/auth/signin', request.url))

        }

   

        return supabaseResponse

    }

   

    export const config = {

        matcher: [

            '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',

        ],

    }

    ```

 

---

 

## 환경 변수 설정

 

-   필수 환경 변수 (.env.local):

    ```env

    NEXT_PUBLIC_SUPABASE_URL=your_supabase_url

    NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key

    ```

 

---

 

## 프로젝트 구조

 

```

├── app/

│   ├── (auth)/

│   │   ├── signin/

│   │   └── signup/

│   ├── api/

│   │   └── auth/

│   └── protected/

├── components/

│   ├── ui/           # ShadCN 컴포넌트

│   └── custom/       # 커스텀 컴포넌트

├── hooks/

├── lib/

│   └── supabase.ts   # Supabase 클라이언트 설정

├── types/

│   └── index.ts      # TypeScript 인터페이스

└── utils/

    └── supabase/

        ├── client.ts

        └── server.ts

```

 

---

 

## 데이터 페칭 패턴

 

-   클라이언트 컴포넌트에서 데이터 페칭:

    ```typescript

    'use client'

   

    import { useEffect, useState } from 'react'

    import { supabase } from '@/lib/supabase'

   

    export default function ClientComponent() {

        const [data, setData] = useState(null)

       

        useEffect(() => {

            const fetchData = async () => {

                const { data, error } = await supabase

                    .from('table_name')

                    .select('*')

                   

                if (error) {

                    console.error('Error:', error)

                } else {

                    setData(data)

                }

            }

           

            fetchData()

        }, [])

       

        return <div>{/* 컴포넌트 렌더링 */}</div>

    }

    ```

 

-   서버 컴포넌트에서 데이터 페칭:

    ```typescript

    import { createClient } from '@/utils/supabase/server'

   

    export default async function ServerComponent() {

        const supabase = await createClient()

       

        const { data, error } = await supabase

            .from('table_name')

            .select('*')

           

        if (error) {

            console.error('Error:', error)

            return <div>Error loading data</div>

        }

       

        return <div>{/* 데이터 렌더링 */}</div>

    }

    ```

 

---

 

## 에러 핸들링

 

-   모든 Supabase 쿼리에서 에러 핸들링을 구현하세요.

-   Toast 컴포넌트를 사용하여 사용자에게 에러 메시지를 표시하세요.

-   로딩 상태를 적절히 관리하세요.

 

---

 

## 보안 규칙

 

-   Supabase Row Level Security (RLS)를 활성화하세요.

-   민감한 데이터는 서버 사이드에서만 접근하세요.

-   환경 변수를 통해 API 키를 안전하게 관리하세요.


 

## 서버 활성화 금지

 

- dev / build 등 기본적으로 사용자가 직접 활성화 합니다.

- 시스템 내에서 해당 명령어를 사용하지 마세요

댓글 작성

댓글을 작성하시려면 로그인이 필요합니다.

로그인하기

댓글 1개

4개월 전

Supabase까지 연동되게 만들어 주겠네요.  

 

기존의 g6 REST API에 연동되면서 동작되는 Shadcn을 만들어 보고 싶은데,

 

아직 방법을 잘 모르겠네요

게시글 목록

번호 제목
31
30
29
28
27
26
25
24
22
20
19
18
17
16
14
13
11
10
9
7
6
5
4
3
1