콤퓨우터/프로그래밍 공부

Next.js Full Course 필기 (2)

파란화면 2024. 3. 17. 16:43
반응형

Fireship.io의 "Next.js Full Course" 강의를 듣고 필기했던 내용입니다.

Nav Menu

구현하기 전에:

  • UI 컴포넌트를 어디에 넣을것인가?
    • 여러 페이지에 공유될 것 같은 컴포넌트는 /components에 넣는다.
      • 내비게이션 바는 어디에서나 보이기는 하지만 실제로는 Root layout에만 들어가는 것이다.
      • 그러니까 루트 layout.tsx 파일과 함께 루트 디렉토리에 같이 놓기로(Colocation) 하자.
  • 서버 컴포넌트 or 클라이언트 컴포넌트?
    • 기본적으로 Next.js의 모든 컴포넌트는 서버컴포넌트
    • 하지만 Sign In 버튼처럼 인터랙티브한 기능이 필요하다면 클라이언트 컴포넌트가 나을 수 있다
      • 'use client';
    • 하지만 SEO를 위해 googlebot이 페이지에 접근했을 때에는, 링크를 따라갈 수 있도록 내비바가 있는 편이 나을 것이다.
      • 인터랙티브한 기능이 필요하지 않다면 되도록 서버 컴포넌트로 놔두는 것이 좋다
      • 하지만 인터랙티브 기능도 포기할 수 없다면?
      • 해답: NavMenu의 자식 컴포넌트를 클라이언트 컴포넌트로 만든다

  • CSS 모듈의 사용: CSS class를 직접 기입하지 않고 Styles object의 nav class를 통해 접근
  • Link 모듈의 사용: <a> 태그를 대체, Client-Side Routing 사용
  • Image 모듈의 사용: <img> 태그를 대체

Style

Flexbox를 이용해 모든 오브젝트를 중앙정렬, 이후 루트 레이아웃에서 컴포넌트를 추가

 

정적 페이지

about 페이지는 정적 페이지이다.
/about/page.tsx를 만들어주자.

API Route

/api/contentURL에서 서빙되는 가라-GET API를 만들기 위해 /app/api/content/route.ts 파일을 만든다.

대충 배열에 더미 데이터를 생성한다.

const posts = [
  {
    title: 'Lorem Ipsum',
    slug: 'lorem-ipsum',
    content:
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.',
  },
];

이 가라-"API"의 목적은 데이터를 JSON으로 뿌려주는것이다.

import { NextResponse } from 'next/server';

const posts = [];
export async function GET(req: Request) {
    return NextResponse.json(posts);
}

Dynamic Route - SSR

/app/blog/[slug]/page.tsx를 생성하고 TypeScript interface Post를 생성해주자.

  • 이 Post는 실제 서버에서 fetching할 데이터의 구조를 하고 있으며 위의 "API"가 뿌려줄 데이터의 구조와 일치한다.

이제 BlogPostPage() 컴포넌트를 만들 텐데, 이 컴포넌트는 Params를 Prop으로 가질것이다.

  • 이 강의 내에서는, props와 컴포넌트를 이용할 때 props라는 로컬 인터페이스를 만들 것이다.
  • 이 디렉토리 구조 특성에 따라 URL 파라미터로 slug를 가지는 Params Object가 된다.
  • ts가 싫으면 그냥 {params}: any 를 하면 되지만, 빅-애플리케이션을 만들 때에 이것은 별로 좋은 습관이 아니다.

Post[] 형식인 posts

변수

상수를 선언하고 await fetch() 한다.

  • 주의할 점은 서버 컴포넌트에서 fetch()할 때에는 FQDN을 써야 한다는 것이다.
  • fetch().then((res) => res.json()) 으로 fetch된 데이터를 JSON 형식으로 바꾸어 대입할 수 있다.
    • Modern JS Wizardry

이제 slug가 매칭되는 포스트를 찾는다.

  • JS find() 메소드를 사용한다.
  • const post = posts.find((post) => post.slug === params.slug)!;
    • 마지막의 느낌표는 NOT 연산같은 게 아니라 TypeScript의 Non-null Assertion Operator이다.
    • 별로 좋은 접근법은 아니긴 하다.

 

Static Generation

DB에 잘 변하지 않는 데이터가 (예를 들어, 블로그 포스트 같은 게) 잔뜩 있다고 해보자. 그러면 이 페이지들을 사전에 생성해두면 CDN 캐싱이 가능해지므로 로딩에 유리할것이다.
하지만 예를 들어 블로그 게시글이 1만개쯤 있다고 한다면 이걸 어떻게 생성해서 캐싱할것인가?

해답: 함수 generateStaticParams()을 export
이 함수의 목적은 미리 렌더링해고 싶은 파라미터가 담긴 오브젝트를 반환하는것이다. (이 경우에는, 포스트의 slug values들이 담겨 있는 object)
이렇게 하여 Next가 동적인 데이터를 찾아 미리 렌더링하도록 할 수 있음

revalidate 옵션을 넣는 것을 권장함

User Authentication

Auth.js

npm i next-auth

Catch-All API Route 생성

/api/auth/[...nextauth]/route.ts
이러면 네임스페이스가 생성되어
GET /api/auth/signin, POST /api/auth/signin/:provider, GET /api/auth/signout 등을 자동으로 쓸 수 있다.

NextAuth와 NextAuthOptions을 임포트해주고 GET, POST로 export

import NextAuth from "next-auth";
import type { NextAuthOptions } from "next-auth";
import GithubProvider from 'next-auth/providers/github';

export const authOptions: NextAuthOptions = {
    providers: [
        GithubProvider({
            clientId: process.env.GITHUB_ID!,
            clientSecret: process.env.GITHUB_SECRET!
        })
    ]
}

const handler = NextAuth(authOptions);
export {handler as GET, handler as POST};

next.js에서 ENV Variable을생성하는방법:
/.env 파일을 생성한다. (실제 간단)

GITHUB_ID=
GITHUB_SECRET=
NEXTAUTH_SECRET=

NEXTAUTH_SECRET 토큰을 생성하기 위해,

openssl rand -base64 32

한다.

사용자가 로그인한/로그인하지 않은 상태의 경우에 프론트엔드에서 다른 페이지를 보여주기 위하여-

애플리케이션의 Root에 session provider를 추가하여야한다.

하지만 여기에서는 Root에 직접 provider를 넣는 게 아니라 Client Component인 /AuthProider.tsx을 추가하기로 한다.

'use client';

import { SessionProvider } from "next-auth/react";
type Props = {
    children: React.ReactNode;
}

export default function AuthProvider({children}: Props) {
    return <SessionProvider>{children}</SessionProvider>;
}

this will allow any client side components nested below to this to access the current user

이제 이것을 Root layout.tsx에서 기존 jsx 전체를 wrapping하도록 해놓는다.

  return (
    <AuthProvider>
      <html lang="en">
        <body className={inter.className}>
          <div className='container'>
            <NavMenu/>
            {children}
          </div>
        </body>
      </html>
    </AuthProvider>
  )

SessionComponent를 layout에서 직접 사용하지 않는 이유:

  • 해당 컴포넌트는 client side features를 사용하므로 client라고 명기하지 않으면 오류가 발생함

아무튼 이를 통해 우리는 현재 유저 정보를 클라이언트 사이드 전체에서 접근할 수 있도록 되었읍니다
이것을 체크해보기 위해 AuthCheck 컴포넌트를 만들어보겠다

next-auth에서 useSession 훅을 임포트한다 - 이 훅을 통해 현재 세션과 사용자 상태에 접근할 수 있다
사용자 상태의 예시: authenticated, loading, unauthenticated

이에 따라 UI 로직을 구현할 수 있다.

아래는 로그인 상태에 따라 children을 보여주거나 아니면 please log in이라는 메시지를 보여주는 컴포넌트의 예시이다

'use client';

import { useSession } from "next-auth/react";

export default function AuthCheck({children}: {children: React.ReactNode}) {
    const {data: session, status} = useSession();
    console.log(session, status);

    if(status === 'authenticated') {
        return <>{children}</>;
    } else {
        return <><p>Please log in</p></>;
    }
}

/components/buttons.tsx를 만들어 여러 가지의 컴포넌트를 export해보자

(1컴포넌트당 1파일 원론주의자들도 존재하지만, 한 파일에 여러 컴포넌트를 넣으면 한 라인으로 컴포넌트를 익스포트해줄 수 있고, use client 같은 것도 한 번만 써 주면 된다는 장점이 있을수도 있고 없을수도 있다)

로그아웃 버튼은 매우 간단하게 만들 수 있다

'use client';
import { useSession, signIn, signOut } from "next-auth/react";
export function SignOutButton() {
    return <button onClick={() => signOut()}>Sign Out</button>;
}

로그인 버튼은, 사용자가 로그인했을 때는 버튼을 보여주지 않고 사용자의 아바타를 보여주도록 구현해보자.
이를 위해 useSession() 훅을 이용하여 session과 상태를 불러오도록 하자. 만약 status가 loading이라면 loading indicator를, status가 authenticated이면 대시보드로 가는 링크와 함께 사용자의 이미지 URL을 보여주도록해보자.

주의 사항: next/image로 외부 이미지를 불러오려면 next.config.js에 설정을 추가해주어야한다. 아니면 그냥 good ol' <img src="" />를 쓰는 방법도 있다.

다 만들었다면 이 컴포넌트를 NavMenu에 추가해보자.
NavMenu는 서버 컴포넌트이지만 클라이언트 컴포넌트인 SignInButton을 집어넣는것이 가능하다.

  • 추천되는 패턴: 클라이언트 컴포넌트를 넣을 때에는 Component Tree의 잎(leaves)에 넣자
반응형