Published on

[Next.js][TypeScript] Next.js에서 GetStaticProps 활용 스태틱사이트 만들기

Authors
  • avatar
    Name
    Almer Minified
    Twitter

[Next.js] 타입스크립트로 정적 사이트만들기

넥스트js의 정적 사이트 렌더링에 대해

넥스트Js에선 서버측에서 정적 생성을 위해 GetStaticProps를 사용한다. 원리를 설명하면, 처음에 서버에서 리턴할 파일을 만들어놓은 후에 그 파일을 요청이 올때마다 계속해서 반환하는 것이다. 이럴 경우 두 가지 시나리오를 생각할 수 있다.

  1. 클라이언트에서 요청할 때마다 빌드된 경로면 빌드된 것을 준다
  2. 클라이언트에서 요청할 때마다 빌드가 안 되어 있으면 빌드해서 준다 이렇게 두 가지인데 둘 다 괜찮다. 완전 정적인 호스팅을 제공하는 Github Page같은 경우는 전자로 모든 페이지를 빌드해서 올려놓으면 되고 후자인 경우는 따로 서버를 돌리면서 작동할 수 있는 경우에 쓰면 된다. 예를 들면 ec2에다가 서버를 돌리는 방법이 있다.

GetStaticProps 구현

export async function getStaticProps(context: GetStaticPropsContext) {
const pathSegments = Object.values(context.params || {}).join("/");
const url = {SITE_FULL_DOMAIN}/{pathSegments}
const language = LanguageUtilizer.getLanguageFromSubDomain({
subDomain: SITE_SUB_DOMAIN!,
});

try {
const response = await fetch(urls.apiPostDetail, {
method: "POST",
headers: {
  "Content-Type": "application/json",
  // 추가적으로 필요한 헤더가 있다면 여기에 추가
},
body: JSON.stringify({
  uid: context.params?.uid,
  language: language,
}),
});
if (response.ok) {
const data = await response.json();
const props = {
  ...data,
  ...{
    url,
    language,
  },
};

const core = new Core(props);
core._postFiles.map((item, index) => {
  FileUtils.convertToPublicPath({
    originalPath: item.getOriginalFilePath(),
    relativePath: item.getRelativePath(),
    subDir: item._subDir,
  });
});

FileUtils.convertToPublicPath({
  originalPath: core._post.getOriginalThumbnailFilePath(),
  relativePath: core._post.getThumbnailRelativePath(),
  subDir: core._post._thumbnailSubDir,
});

return {
  props, // props를 그대로 반환
};
} else {
throw new Error(Server returned an error: {response.status});
}
} catch (e) {
// 에러 처리
ResponseUtilizer.controlError({ e, message: "getStaticProps" });
// 에러 객체에서 메시지를 추출하여 에러를 던진다
// throw new Error(
//   getStaticProps 에러 발생: {e instanceof Error ? e.message : e}`}

위 코드를 보면 GetStaticProps에서 이미 본래 데이터를 가지고 있는 서버와 통신하고 있음을 알 수 있다. 본서버와 통신한 후 그 데이터를 바탕으로 이 NextJs를 실행하고 있는 서버에서 할 일을 다 처리한 후에 props를 리턴한다. 이 경우에 기존 서버사이드 렌더링처럼 props를 받아 컴포넌트들을 구성하는 것이다. 여기서 접근할 수 있는 것에 주의해야 하는데 클라이언트에서 확인할 수 있는 브라우저창의 주소같은 것은 접근할 수가 없다. 만약 그런걸 처리해야 한다면 추측해서 SITE_SUB_DOMAIN 같은 변수로 처리하면 된다.

GetStaticPath 구현

export async function getStaticPaths() {
const subDomainSuffix = LanguageUtilizer.getSuffixFromSubDomain({
subDomain: SITE_SUB_DOMAIN!,
});
const language = LanguageUtilizer.getLanguageFromSubDomain({
subDomain: SITE_SUB_DOMAIN!,
});

if (isNaN(Number(subDomainSuffix))) {
// 여기는 suffix가 숫자일 때만 허용
console.log("패스가 없었음");
return { paths: [], fallback: false };
}

try {
const response = await fetch(urls.apiPostDetailPath, {
method: "POST",
headers: {
  "Content-Type": "application/json",
  // 추가적으로 필요한 헤더가 있다면 여기에 추가
},
body: JSON.stringify({
  sub_domain_suffix: subDomainSuffix,
  language: language,
}),
});

if (response.ok) {
const data = await response.json();
// 각 게시글 id를 기반으로 경로를 생성
const paths = data.uids.map((uid: string) => ({
  params: { uid: uid },
}));
return {
  paths: paths,
  fallback: false,
};
} else {
throw new Error(
);
}
} catch (e) {
// 여기에서 오류 객체는 AxiosError가 아니므로 적절한 타입으로 변환
ResponseUtilizer.controlError({ e, message: "getStaticProps" });
throw new Error(
);
}

이 코드를 보면 알 수 있듯 위에서 생길만한 의문을 해결해준다. 위에서 getstaticprops라고 하면 만약 파일명이 [somedynamicvalue].tsx 같은 경우였다면 어떻게 구현하는가 라는 의문이 생길 수 있었다. 이 경우엔 그런 다이나믹 라우팅을 가능하도록 getstaticprops에 보낼 패스들을 getstaticpath를 통해 생성해주는것이다. getstaticprops가 아니라 getserversideprops였다면 직접 url을 context를 통해 가져올 수 있었지만 이건 빌드할때 알아야 하므로 그 작업을 getstaticpath가 해주는 것이다. getstaticpath가 만들어야하는 path들을 알아서 처리한 후에 그 값들을 getstaticprops로 넘겨 마치 getserversideprops가 작동하듯이 작동할 수 있게 해주는 것이다.

클라이언트 정보를 받아와서 화면을 구성하는 방법

모두 다 이렇게 정적으로 만들어진다면 만약 클라이언트에서 화면을 동적으로 구성하는 것은 어떻게할까? 그건 useeffect를 이용하면 된다. 물론 SEO는 좋지 않을지 모른다. 구글 봇이 뛰어나서 클라이언트사이드렌더링도 처리한다고는 하지만 나머지 봇들은 이런 능력이 부족할 수 있으니 이건 감안하자.

    const [counter, setCounter] = useState(5);
    useEffect(() => {
      const timer = setInterval(() => {
        setCounter((prevCounter) => {
          if (prevCounter <= 1) {
            return 0;
          }
          return prevCounter - 1;
        });
      }, 1000);

      return () => clearInterval(timer);
    }, []);

이런식으로 useeffect는 사용할 수 있다. 그렇기때문에 저렇게 타이머를 주지 않고 바로 클라이언트 화면을 구성한다면 state도 동시에 활용할 수 있으므로 거의 대부분의 기능을 구현할 수 있다. 하지만 역시 SEO가 거슬린다면 이 부분을 getstaticprops에서 최대한 처리해줘야 할 것이다. 위 코드처럼 하기 싫다면 아래처럼 해보자.

    useEffect(() => {
      return () => null;
    }, []);

이건 클라이언트에서 화면이 한번 생성될 때 바로 실행된다. 사용자는 별다른 점을 느끼지 못한다. 다만 봇의 SEO만이 조금 걸릴 뿐이다.