Next.js App Router에서 URL을 통해 Open Graph 정보 가져오기

정유정 | yujeong choung·2023년 6월 4일
1

Next

목록 보기
3/3

사내에서 새롭게 웹 페이지를 리뉴얼 하면서 경험하게 된 내용들을 하나씩 기록하려 한다.

현재 프로젝트는 Next.js 13.4.1 버전의 app router를 활용하고 있다.

이번 글에서는 사용자가 게시글에다가 URL을 적었을 때, 그 URL이 가리키는 페이지의 Open Graph 정보를 가져 온 방법에 대해 적어보려고 한다.

Open Graph

우선 Open Graph란, URL을 소셜 미디어에서 공유하게 됐을 때 보여지는 모습을 정의해주는 프로토콜이다.

Open Graph 태그들은 웹페이지의 <head></head> 섹션에 위치하며, meta 태그를 사용하여 해당 웹 페이지의 title, description, image, url등을 정의 해둘 수 있다.

예를들어, 네이버 URL(www.naver.com)을 카카오톡에 공유하게 된다면 아래처럼 자동으로 생성된 미리보기 화면을 만나게 된다.

이를 네이버 사이트에서 meta 태그로 확인 해보면 아래처럼 미리 정의되어 있는 것을 확인할 수 있다.

Metadata를 가져오기 위한 라이브러리 고르기

사용자가 공유한 URL을 통해 metadata를 가져오기 위한 라이브러리를 고르는것이 가장 먼저 해야할 일이였다.

우선 매우 많은 라이브러리들이 있었는데 그 중에서도 고려했던 점은 아래와 같다.

  • 마지막 업데이트가 너무 오래전에 일어나지 않았어야 한다.
  • 위클리 다운로드 수가 너무 적지 않아야 한다.
  • Next.js 13의 app router에서 활용할 수 있어야 한다.

위의 내용들을 최대한 고려하여 처음으로 선택 했던 라이브러리는 open-graph-scraper 였다. 위클리 다운로드 수도 적지 않았고, open-graph-scraper는 Fetch API를 사용하고 있기 때문에 Fetch Web API 위에서 작동하고 있는 Next.js의 app 디렉토리에서도 활용성이 좋을 것이라 생각됐다.

open-graph-scraper 활용 실패

하지만 open-graph-scraper를 사용하려고 했을 때 에러가 발생헸다.

  • 첫번째는 vercel에 배포했을 때 뜨게되는 node 버전의 문제였다.

open-graph-scraper는 한달전에 버전 5 ⇒ 버전 6으로 업그레이드 되면서 package.json 에 node 버전을 명시해두었다.

"engines": {
	"node": ">=18.16.0"
},

로컬에서 빌드를 하게 됐을 때는 문제 없이 빌드가 되었는데 repo에 연결된 vercel에서 빌드를 실행 할 때는 아래와 같은 에러가 발생했다.

vercel에서도 사용 가능한 가장 최신의 Node.js LTS 버전이 선택된다고 하는데 따로 노드 버전에 대해서 명시해두지 않았음에도 LTS 버전인 18.16.0이 아닌 18.15.0이 선택된 부분에 대한 의문이 들었다.

다른 패키지에 디펜던시라던지 해당 부분에 대해 조금 더 디버깅 해보고 싶었으나 프로젝트 일정상 다른 해결 방법을 찾을 필요가 있어서 (이 부분은 필자가 후에 조금 더 찾아보려고 한다.) 우선 라이브러리를 버전 5로 다운그레이드 시켜서 사용해보고자 하였다.

하지만 버전을 다운그레이드 했을 때는 또 다른 문제에 부딪혔다.

  • 두번째는 웹팩 워닝이였다.

open-graph-scraper의 경우에는 디펜던시로 걸려있는 패키지 중에 keyv에서 해당 웹팩 워닝이 뜨게 되었다. 해당 부분은 keyv 깃헙에 여러 이슈로도 올라와 있었는데 아직 명확한 해결법은 없는 상황이였다.

해당 워닝을 해결하기 위해서는 기존 Next.js의 pages 디렉토리 안에 api 라우트를 사용하거나, @keyhq/core를 통해 해당 이슈를 해결할 수 있지만 필자는 app 디렉토리 안에 Route Handlers를 사용하고자 했고, 라이브러리의 경우에는 got 라이브러리에서 사용중인 cacheable-request와 같은 업스트림 디펜던시들도 모두 변경이 필요해 보였다.

따라서 다른 라이브러리를 찾아보고자 하였고, url-metadata를 활용하게 되었다.

url-metadata 활용하기

처음에는 CSR 컴포넌트 안에서 해당 라이브러리를 바로 사용하고자 하였다.

const PreviewCard = ({ externalUrl }: Props) => {
	const [ogData, setOgData] = useState({})
	
	const getOgData = async () => {
	   const result: urlMetadata.Result = await urlMetadata(externalUrl, {
	     cache: 'force-cache',
	   })
	
	    setOgData({
	      ogTitle: result['og:title'],
	      ogUrl: result['og:url'],
	      ogImage: result['og:image'] || result['image'],
	    })
	}
	
	useEffect(() => {
	   getOgData()
	}, [])

	...
}

사용자의 게시글을 구성하는 컴포넌트 중에 컨텐츠를 보여주는 컴포넌트 안에서 불러오고자 했는데 간과 했던 부분이 존재했다. 바로 CORS 였다. (CORS는 필자의 다른 글에서 조금 더 살펴보면 좋을 것 같다.)

이를 해결하고자 사용한 것이 Next.js의 Route Handlers 였다.

app 디렉토리에서 Route Handlers 활용하기

라우트 핸들러는 기존 pages 디렉토리에서 활용하던 api routes와 동일한데, 라우트 핸들러를 활용하면 Web Request와 Response API를 사용하여 지정된 라우트에 대해 커스텀 리퀘스트 핸들러를 만들 수 있다는 장점이 있다.

CORS는 브라우저단에서 나타나는 에러이기때문에 Route Handlers를 활용하면 해당 에러를 해결할 수 있게 된다.

app 디렉토리내의 route.js|ts로 만든 파일은 모두 Route Handlers가 되는데 route 파일 위에 설정해 준 디렉토리가 해당 route의 경로가 된다.

// app/api/preview/routes.ts

import { NextResponse } from 'next/server'

import urlMetadata from 'url-metadata'

export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url)
    const url = searchParams.get('url') || ''

    const result: urlMetadata.Result = await urlMetadata(url, {
      cache: 'force-cache',
    })

    const ogData = {
      ogTitle: result['og:title'],
      ogUrl: result['og:url'],
      ogImage: result['og:image'] || result['image'],
    }

    return NextResponse.json(ogData, { status: 200 })
  } catch (err) {
    return NextResponse.json(
      { error: 'Internal Server Error' },
      { status: 500 },
    )
  }
}

필자는 위의 코드처럼 Route Handler를 설정해주고 getOgData 부분을 아래처럼 변경해주었다.

const PreviewCard = ({ externalUrl }: Props) => {
	const [ogData, setOgData] = useState<OgData>({
    ogTitle: '',
    ogUrl: '',
    ogImage: '',
  })

  const getOgData = async () => {
    const { data }: AxiosResponse<OgData> = await axios(
      `/api/preview?url=${encodeURI(externalUrl)}`,
    )
    setOgData(data)
  }

  useEffect(() => {
    getOgData()
  }, [])

	...
}

이제는 CORS 문제 없이 만들어 둔 Route Handler(api/preview)를 통해 response가 잘 내려오는것을 확인할 수 있다.


Next.js의 app 디렉토리를 사용하면서 pages 디렉토리와의 변경점이 많아 새롭게 배우게 되는 부분들이 많은 것 같다.
app 디렉토리 내에서 활용을 잘하면 좋을 것 같은 부분들이 많아서 (intercepting 라우터라던지, parallel 라우터라던지..) 앞으로 프로젝트에서 새롭게 적용하게되는 부분들을 그냥 넘기지 말고 기록 해봐야겠다!

profile
언제나 새로운 도전을 꿈꾸는 개발자

1개의 댓글

comment-user-thumbnail
2023년 12월 26일

저도 갑자기 이거 하라고해서 뭐써야 되는지 난감햇는데 덕분에 도움이 되었습니다

답글 달기