avatar
Published on

nextjs 서버사이드에서 absolute url 가져오기

Author
  • avatar
    Name
    yceffort

nextjs에서 fetch api를 사용하다가 깨닫는 점 하나는, (당연하지만) 서버와 클라이언트에서 fetch를 하는 방식을 다르게 가져가야 한다는 것이다. 무심코 평소에 하듯이 fetch('/api/some/info')를 하다보면, getServerSidePropsgetInitialProps에서 에러가 날 수 있다. fetch 를 할 때 놓치지 말아야 할 이 에러를 살펴보자.

Absolute URL

서버사이드에서 절대 경로가 아닌 상대경로로 fetch 요청을 하면 (/api/some/info) Absolute URL이 필요하다는 에러가 뜬다. 클라이언트에서는 origin을 추론할 수 있기 때문에 상대경로로 요청을 해도 상관없지만, 서버사이드에서는 현재 주소가 무엇인지 알리가 없기 때문에 상대경로로 요청할 수 없다.

그렇다면 아래와 같이 처리해도 되는 것인가?

async function getUser(id: number) {
  const response = await fetch(
    // 서버라면 강제로 우리가 알고 있는 absolute url을 주입
    typeof window === 'undefined'
      ? 'https://yceffort.kr'
      : '' + `/api/user/${id}`,
  )
  const result = await response.json()
  return result
}

absolute url, origin이 고정되어 있는 경우라면 괜찮겠지만 그렇지 않다면 이렇게 처리하는건 안전하지 못하다. 따라서 우리는 absolute URL을 추론해야 한다. 추론할 수 있는 가장 좋은 방법은, ctx.reqIncomingMessage를 사용하는 것이다. 여기에는 요청과 관련한 정보가 포함되어 있는데, 이 요청이 날라온 곳이 absolute url이라고 가정하고 코딩하는 것이다.

import { IncomingMessage } from 'http'

function getAbsoluteURL(req?: IncomingMessage) {
  // 로컬은 http, 프로덕션은 https 라는 가정
  const protocol = req ? 'https:' : 'http:'
  let host = req
    ? req.headers['x-forwarded-host'] || req.headers['host']
    : window.location.host

  // 주소에 local이라는 문자열이 들어가 있다면,
  // 또는 별도의 환경변수를 주입하고 있다면 그것을 사용해도된다.
  // process.env.RECT_APP_PROFILES === 'local'
  if ((host || '').toString().indexOf('local') > -1) {
    // 개발자 머신에서 실행했을 때 로컬
    host = 'localhost:3000'
  }

  return {
    protocol: protocol,
    host: host,
    origin: protocol + '//' + host,
  }
}

물론 이 방법도 완전하지는 못하다. 프로젝트의 상황에 따라 조금씩 다를 수 있다. 일단 프로토콜은 서버에서 추론할 수가 없는 부분이기 때문에 하드 코딩 형태의 추론이 필요하다.

그리고 host가 아닌 x-forwarded-host를 사용한 이유는 설명에도 나와있듯, 요청을 처리하는 원래 사용된 host를 확인하기 위해 사용하였다.

그리고 이제 fetch 함수는 getAbsoluteURL를 사용해야 한다.

type FetchOptions = {
  req?: IncomingMessage
}

async function getUser(id: number, options?: FetchOptions) {
  const absoluteURL = getAbsoluteURL(options?.req).origin
  const response = await fetch(
    options?.req ? absoluteURL : '' + `/api/user/${id}`,
  )
  const result = await response.json()
  return result
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { req } = ctx
  const userId = ctx.query?.userId

  const user = await getUser(userId, { req })
  return {
    props: {
      user,
    },
  }
}

이제 서버에서 요청을 할때는 req 정보를 넘겨줘야 한다. 서버와 클라이언트 요청은 명확히 구별할 수 있으므로 크게 어렵지 않을 것이다.

또다른 방법

아무래도 하드 코딩이 들어가 있기 때문에, absolute url을 알아낼 수 있는 또다른 방법은 실행시에 환경변수로 주입하고, 이를 가져오는 것이다. 이 방법을 쓰고 있는 것이 VERCEL이다. NEXT_PUBLIC_VERCEL_URL를 사용하면 이 빌드가 실행되는 곳의 주소를 알아낼 수 있다.

물론 이는 devpos나 개발 환경에서 이러한 정보를 제공할 수 있는 환경 구축이 선행되어야 한다.