글로벌 서비스를 개발하는 도중, 백엔드 개발자님으로부터 "favicon.ico를 호출할 수 없다"는 에러 로그를 받았습니다. 이 로그의 의미와 문제를 해결하는 과정을 공유합니다.

Next JS 13.4.2, app directory를 사용하고 있습니다.

Next JS 에서 제공하는 기본 에러 페이지와 i18n 폴더 구조

API 에러 서버 로그에 favicon.ico를 호출할 수 없다는 로그가 꽤나 많이 찍혀있습니다. 저희 프로젝트에서 favicon.ico는 사용하고 있지 않은데 어디서 발생하는 걸까요?

간혹 네트워크 탭에 찍히는 favicon.ico를 보고 에러 발생 시 호출되는 것을 유추해볼 수 있었습니다.

routing 구조에 없는 페이지에 접속해 에러 페이지를 호출 시켜봅시다. ex) localhost:3000/xxx/yyy

localhost:3000/favicon.ico가 호출되는 곳에서 왜 에러로그가 찍힐까요? 먼저 mdn과 같이 다국어를 지원하는 페이지를 보면 사용자의 언어 토큰을 도메인 바로 뒤에 붙이는 걸 확인할 수 있습니다.

Next 또한 이러한 구조를 가질 수 있는 폴더 구조를 제안하고 있습니다. 간략하게 설명하자면 app 디렉터리 하위에 다양한 언어 토큰을 받을 수 있는 [lang] Dynamic Routes를 세팅합니다.

main 페이지는 어떤 작업을 하고 있을까요? main 페이지에서는 이 lang 정보를 이용해 해당 언어의 main 데이터를 가져와 페이지를 렌더링하고 있습니다.

page.tsx
1// 예시
2// sever component
3const MainPage = async ( { params: { lang } } ) => {
4 const mainData = await fetch('/api/main', { lang })
5}

로그의 원인은 이 부분에 있습니다. localhost:3000/favicon.ico이 호출될 때, lang위치에 favicon.ico가 전달되면서 main 페이지에 에러 로그가 쌓이고 있었습니다.

커스텀 에러 페이지 구현

localhost:3000/favicon.ico 호출을 막아봅시다. 가장 먼저 떠오르는 생각은 기본적으로 제공하는 에러 페이지를 막는 것입니다.

Next JS에서는 React의 Error boundary를 이용해 Error를 감지, 에러 페이지를 호출합니다. 문서 기본적으로 error.tsx 파일을, NotFound 함수를 호출하면 not-found.tsx 파일을 호출합니다.

에러가 발생한 곳에서 가장 가까운 error, not-found 페이지를 찾아 노출합니다. 현재 다루고 있는 에러 페이지는 단순 404, 500 페이지이기 때문에 app 폴더 밑에 파일들을 위치해 둡니다.

not found 페이지 위치

not found 파일을 app 폴더 밑에 두면 지정하지 않은 segment로 진입했을 때 자동으로 not found 페이지를 노출합니다. (참고)

하지만 i18n 폴더 구조상 발생하는 이슈가 있습니다. 서비스에서 제공하는 에러 페이지는 페이지의 title, description, 이전 페이지로 이동하는 버튼이 있습니다. 이 언어를 번역하는 provider가 [lang] 폴더 밑에 위치하고 있어 접근할 수 없는 문제가 있습니다.

  • provider: app/[lang]/provider.tsx
  • not-fouund: app/not-found.tsx

번역을 하기 위해선 not-found 파일을 [lang] 밑으로 이동시켜야 합니다. app 폴더 밑에 위치하지 않았기 때문에 자동으로 not found 파일을 감지할 수 없어 수동으로 notFound 함수를 호출해야 합니다.

지금 접근한 페이지가 존재하지 않는 페이지를 감지하기 위해 Catch-all Segments를 이용합니다. coloso.global/[lang] 밑의 모든 segment를 감지하고 notFound를 return 하는 방식으로 해결합니다.

  • not-found: app/[lang]/not-found.tsx
  • 404 감지 컴포넌트: app/[lang]/[...404]
not-found.tsx
1// app/[lang]/[...404]
2import { notFound } from 'next/navigation';
3
4const NotFound = () => notFound();
5
6export default NotFound;

마치며

커스텀 에러 페이지를 구현하며, 기본 에러 페이지를 호출했을 때 발생한 favicon.ico 호출을 해소할 수 있었습니다. 남은 과제로는 public 밑 정적 파일이나. robots.txt, sitemap.xml과 같은 경로로 들어올 때도 favicon.ico가 호출되는데요, 후속으로 이 작업도 삽질해 볼 예정입니다.

2024.03.24 수정

해당 이슈에 대해선, 브라우저에서 제공하는 기본 favicon.ico의 문제로 밝혀졌습니다.

issue #46918, PR #62511

참고

  1. i18n 폴더구조: https://github.com/vercel/next.js/tree/canary/examples/app-dir-i18n-routing
  2. 컴포넌트 계층 구조: https://nextjs.org/docs/app/building-your-application/routing#component-hierarchy
  3. not found 페이지: https://nextjs.org/docs/app/api-reference/file-conventions/not-found
  4. Catch-all Segments: https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#catch-all-segments