Next Js 이모저모

김선은·2023년 10월 16일

NextJs !

일단 nextjs의 관련된 걸 적어두고, 나중에 세부 게시물로 더 자세히 써보자!

ssr 관련된 기술은 따로 정리하기.
https://leetrue-log.vercel.app/nextjs-12-rendering
정리된 블로그도 다시 정독하고 참고하자!

Next Image?

import Image from "next/image"; 로 사용하는 Image 컴포넌트의 특징을 알아보자.

  1. 성능 최적화: 최적화 기능을 다양하게 제공한다. 이미지 리사이징, 최적화, 브라우저별 최적화 등을 자동으로 처리해 페이지 로딩 속도를 향상시킨다.

  2. 레이아웃 지원: layout 속성으로 이미지 렌더링의 방식을 지정할 수 있다.
    layout="intrinsic"이 기본값. 이미지의 원래 크기.
    responsive는 부모에 맞춤. 뷰포트 크기에 따라 달라짐,
    fixed
    fill 이 있다.

  3. lazy loading 지원: SSR과 함께 사용, 서버에서 이미지를 최적화하며 페이지 렌더링 속도 향상. (모양새를 미리 보여주기에 로딩 화면이 좀 더 낫다.)

참고 할 블로그!
Next Image 컴포넌트 가이드
Next/Image를 활용한 이미지 최적화

페이지를 불러오는 방식

페이지 컴포넌트 작성

  • nextjs가 제공하는 기본 방법으로 초기 state를 가지고 HTML로 바꿔준다.
  • 페이지 컴포넌트를 작성하면 페이지 처음 상태를 next가 알아서 HTML로 렌더링 해준다.
  • 리액트가 클라이언트를 불러오고, API에서 데이터를 불러온다.

getServerSideProps 함수

  • 서버단에서 데이터를 불러온다. html 자체에 데이터가 있게 됨.
  • api에서 데이터가 오는 게 아니여서 로딩 상태를 유저가 못본다.
  • 데이터를 불러와서 모든 데이터를 한 번에 표시해야 하기에 데이터를 늦게 불러오는 환경이라면 HTMl 생성이 느려서 페이지가 늦게 뜸
  • 리턴 값을 컴포넌트에서 데이터로 전달 받음

getStaticProps 함수 - 빌드 타임에 한번만 생성

  • 데이터를 이용해 여러 페이지 생성 가능
  • 사이트를 빌드할 때 미리 페이지 생성, 정적 페이지에 유용. ex) FAQ페이지
  • 동적 url인 경우는 getStaticPaths로 페이지 개수를 미리 알려줘야한다.

1. 커스텀 훅과 미들웨어

useUser 커스텀 훅

  • api/users/me/index.ts 에서 req.session을 확인함
  • libs/client/useUSer.ts 에서 api/users/me을 useSWR로 데이터를 가져옴
export default function useUser() {
  const { data, error } = useSWR<ProfileResponse>("/api/users/me");
  const router = useRouter();

  useEffect(() => {
    if (data && !data.ok) {
      router.replace("/login");
    }
  }, [data, router]);

  return { user: data?.profile, isLoading: !data && !error };
}

미들웨어를 추가한다면

  • root에 middleware.ts
  • useUser보다 더 먼저 실행됨. 쿠키가 없다면 미들웨어가 막아버려서 useUser가 실행이 안됨
  • 단순히 유저 쿠키 여부 확인해보기
  • useUser와 verification 과정이 다르다!

    useUser는 실제로 DB에 요청을 보내서 응답을 받아서 확인한다. 미들웨어로 단순하게 유저의 쿠키 여부를 본다면 쿠키가 유효기간이 지났거나 변형됐거나 하는 가능성이 있음!

export function middleware(req: NextRequest, ev: NextFetchEvent) {
  console.log(req.ua);
  if (req.ua?.isBot) {
    return new Response("봇은 안돼요", { status: 403 });
  } //status도 추가 가능.
  if (!req.url.includes("/api")) {
    if (!req.url.includes("/enter") && !req.cookies.carrotsession) {
      return NextResponse.redirect("/enter");
    }
  }
}

nextjs의 미들웨어

  • req.ua에 담긴 정보로 next가 bot 여부를 확인해주기도 하고, 유저의 브라우저 정보도 확인이 가능함
  • api 요청이 있을 때도 미들웨어가 실행됨
  • 로컬호스트에서는 작동하지 않지만 미들웨어에서 req로 유저의 위치 정보 알아낼 수 있음.
  • 도시,위도,경도. 국가에 따라 접근 여부도 설정 가능.
  • NextResponse로 rewrite, json 보내기도 가능.
export const middleware = async (req: NextRequest, ev: NextFetchEvent) => {
  const res = NextResponse.next();
  const session = await getIronSession(req, res, {
    cookieName: "carrotsession",
    password: process.env.COOKIE_PASSWORD!,
    cookieOptions: {
      secure: process.env.NODE_ENV! === "production", // if you are using https
    },
  });

  if (!req.url.includes("/api")) {
    return;
    if (!session.user && !req.url.includes("/login")) {
      const url = req.nextUrl.clone();
      url.pathname = "/login";
      return NextResponse.rewrite(url);
    }
  }
};

NextJs 업데이트 확인하기

https://nextjs.org/docs/pages/building-your-application/routing/middleware
https://nextjs.org/docs/messages/nested-middleware

2. Dynamic Import

  • 유저가 컴포넌트를 필요할 때만 코드를 불러오게 할 수 있다.
  • 컴포넌트를 lazy loading
  • 코드 최상단의 컴포넌트 import는 사용여부와 상관없이 불러짐.
  • suspense 이용 시 컴포넌트 로딩중 placeholder 보여줄 수 있다.
import dynamic form "next/dynamic"

const Bs = dynamic(() => import("@components/bs"),
{ ssr: false, loading: () => <div>loading ... </div> });

//사용예시
 return ( 
<>
<Bs /> 
</>
)
  • ssr: true 값을 false로 사용할 수 있고 loader 컴포넌트를 보여줄 수있음.

React의 Suspense로 loader 기능 사용하기

  • suspense 쓰면 useSWR hook의 loading states 대체 가능
return (
<>
  <Suspense fallback="로딩 중">
    <Bs />
  </Suspense>
</>
)

3. _document

  • 뼈대. next가 최적화를 도와줌!
  • 앱 컴포넌트와 다르게 도큐먼트 컴포넌트는 서버에서 한 번만 실행됨
  • 페이지 렌더링 시 사용되는 html, body 태그를 업데이트 할 수 있다
  • 빌드 시 원래 유저가 다운로드 해야하는 파일을 NextJs가 변환하고 대체해준다
  • link로 추가한 구글 폰트를 NextJs가 style 태그로 바꾸고 다운로드 해서 파일 안에 넣어줌
import Document, { Head, Html, Main, NextScript } from "next/document";

class CustomDocument extends Document {
  render(): JSX.Element {
    console.log("DOCUMENT IS RUNNING");
    return (
      <Html lang="ko">
        <Head>
          <link
            href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default CustomDocument;

Script Component

  • next/script는 HTML script 태그의 확장
  • 써드 파티 스크립트의 로드 우선 순위를 설정 할 수 있음

beforeInteractive: 페이지가 interactive 되기 전에 로드
afterInteractive: (기본값) 페이지 먼저 불러온 뒤에 스크립트 로드
lazyOnload: 다른 모든 데이터나 소스를 불러온 후에 로드
worker: (실험적인) web worker에 로드

import Script from 'next/script'

< Script src="https://connect.facebook.net/en_US/sdk.js" strategy="lazyOnload" / >
  • 스크립트를 다 불러오고 나서 실행되게 onLoad 함수 사용. (스크립트 다 불러오고 난 뒤에 실행되는 함수)
  • 외부 스크립트나 SDK 사용할 때 유용

4. getServerSideProps

  • clinet단이 아닌 sever에서 데이터를 가져오기
  • props가 있는 오브젝트를 반환해주면 된다.
  • prisma의 client를 서버단에서 쓰고 SWR코드를 지우면 swr이 해주던 data를 가져왔을 때는 자동으로 api를 다시 불러주는 기능을 못쓰게 됨
  • static optimization이나 cache 사용 할 수 없게 된다.
export async function getServerSideProps() {
  const products = await client.product.findMany({});
  return {
    props: {
      products: JSON.parse(JSON.stringify(products)),
    },
  };
}
// export default하는 함수에게 getServerSideProps가 요청하는 데이터 전달

SWR 편리한 기능: 캐시에 있는 데이터 변경, swr이 자동으로 데이터를 갱신해최신 데이터로 볼 수 있음

getServerSideProps(context) 로 인증 처리

  • context 객체가 있음. 안에 살펴보면 iron session이 필요로 하는 쿠키가 담겨있다.
  • profile 페이지는 useUser 훅을 쓰는데 이것은 DB에서 req.session으로 유저의 프로필 요청하는 중.
  • getServerSideProps의 props로 client.profile 전달 -> Page 함수에서 prop 받아서 api/user/me 로 fallback하기
  • getServerSideProps(ctx: NextPageContext)
  • useSWR만 썼을 때와 다르게 서버에서 바로 유저 세션을 확인해 로딩이 없음.

SWR과 서버단 코드를 같이 쓴다?!

  • SWRConfig 태그로 원하는 페이지 컴포넌트를 감싼 함수를 새로 만든다.
  • fallback props에 api 키값을 넣어서 캐시의 초기값을 지정
  • getServerSideProps 함수에서 prisma의 client 코드 작성 (기존 api 폴더에 있는 내용을 옮긴다)

즉 원하는 사이트의 렌더링을 서버단에서 미리 설정해두고, useSWR의 fallback을 이용해서 컴포넌트의 캐시 초기값을 설정한다.

const Page: NextPage<{ products: ProductWithCount[] }> = ({ products }) => {
  return (
    <SWRConfig
      value={{
        fallback: {
          "/api/products": {
            ok: true,
            products,
          },
        },
      }}
    >
      <Home />
    </SWRConfig>
  );
};

export default Page;

5. getStaticProps와 친구들

  • getStaticProps는 빌드 시 정적 페이지 생성. 한 번만 동작함
  • HTML을 업데이트 하고 싶다면 revalidate 옵션에 시간 설정을 해줌.
  • revalidate는 특정 페이지, 동작에서도 사용 가능(포스트 생성api)
  • 동적인 페이지들도 정적 페이지로 생성 가능(getStaticPaths 함께 사용)
  • getStaticPaths의 paths 속성에 미리 만들 사이트가 무엇인지 알려주거나, fallback 옵션 사용으로 요청 시 정적 페이지 생성 가능.

getStatcitProps로 정적 페이지의 데이터를 가져올 때, 동적 url의 사이트에서는 getStaticPaths도 함께 사용한다. 빌드 타임에 어떠한 페이지들이 만들어져야하는지 nextjs에게 알려줘야 함.

getStaticPaths의 paths

정적 생성해야하는 경로들의 배열. 동적 경로를 어떻게 생성할지 정보를 제공. 빈 배열임은 동적 경로가 아직 정의되지 않았음을 의미.

getStaticPaths의 fallback

1) blocking: 요청된 경로가 정적으로 생성되지 않은 경우 nextJs는 서버에서 페이지를 생성하고 반환한다. (true와 백그라운드 작업이 같음)
2) false: 정적으로 생성되지 않은 경로는 404 페이지로 처리
3) true: 요청 경로가 정적으로 생성되지 않은 경우 빈 페이지 반환,클라이언트 측에서 데이터를 가져와서 업데이트. 로딩 화면을 보여줄 수 있는 차이점!

api 핸들러에서 data 받아오던 것들을 getStaticProps로 바꿀 수 있다.

📂pages/products/[id].tsx
// api 핸들러에서 return해준 res.json을
res.json({
  ok: true,
  product,
  relatedProducts,
  isLiked,
});
----------------------
// 동적 url이기에 같이 써주는 paths.
export const getStaticPaths: GetStaticPaths = () => {
  return {
    paths: [], // 빈배열로 실제 값을 주진 않았음.
    fallback: "blocking", // 사용자가 요청시 생성함
  };
};

// getStaticProps에서 props로 리턴해준다. api ts파일의 코드도 옮긴다고 가정.
export const getStaticProps: GetStaticProps = async (ctx) => {
return {
    props: {
      product: JSON.parse(JSON.stringify(product)),
      relatedProducts: JSON.parse(JSON.stringify(relatedProducts)),
      isLiked,
    },
  };
}

// fallback: true 일 때 로딩 ui 보여주기
📂pages/propducts/[id].tsx

if (router.isFallback) {
	return(<span>로딩중</span>)
}
  

6. ISR(Incremental Static Regeneration)

  • 정적 웹 사이트 생성(Static Site Generation, SSG)과 함께 사용되는 기술
  • ISR은 빌드 타임에 HTML 파일을 생성
  • ISR는 정적 페이지의 캐시를 효과적으로 업데이트하고 즉각적인 데이터 변화를 반영하는 데 사용
  • ISR은 동적 데이터를 쉽게 정적 페이지에 통합하고 업데이트하는 데 유용
  • 페이지를 즉시 불러올 수 있으며 서버단에서 렌더링 해주지 않아도 됨. 유저는 기다릴 필요가 없다.
export async function getStaticProps() {
  const posts = await client.post.findMany({ include: { user: true } });
  return {
    props: {
      posts: JSON.parse(JSON.stringify(posts)),
    },
    revalidate: 10,
  };
}
  • getStaticProps가 빌드 시 한번만 실행된다는 단점을 백그라운드에서 ISR 업데이트하는 것으로 보완해준다.
  • 리액트로 생성하는게 아니고 NextJs가 HTML을 불러옴
  • NextJS가 백그라운드에서 HTML을 재생성! (regeneration을 트리거)
  • 테스트 시 프로젝트를 빌드한 다음 npm run start 실행으로 확인

7. On-demand Revalidation

  • 수동으로 getStaticProps를 작동시킬 수 있게 됨
  • api handler에 revalidate 코드를 추가시키면 된다. (유저가 작성하고 새로운 글을 등록 -> api POST handler 코드에 추가하기)
  • 페이지가 정적으로 생성되었지만, res.revalidate 옵션으로 캐시된 페이지를 재생성하는 시간 간격을 설정할 수 있다.
async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  if (req.method === "POST") {
    const {
      body: { question },
      session: { user },
    } = req;
    const post = await client.post.create({
      data: {
        question,
        user: {
          connect: {
            id: user?.id,
          },
        },
      },
    });

    await res.revalidate("/community");

    res.json({
      ok: true,
      post,
    });
  }

NextJs의 ODR 문서

profile
기록은 기억이 된다

0개의 댓글