cloudfront를 사용한 Next.js 사용시 serversideProps에서 useragent 감지하는 방법

June 14, 2022

현재 진행하고 있는 프로젝트에서 Next.js와 Amplify를 사용하고 있었습니다. Amplify는 기본적으로 cloudfront + lambda + S3로 구성되는 인프라를 구축하고 있었으므로, 배포에 대한 수고로움을 줄이고자 Amplify를 사용했으나, 최근에 문제가 발생하여 변경이 필요했습니다. 이번 포스트는 이 내용을 잊지 않기 위해, cloudfront에서 사용하는 useragent에 대한 캐시 정책과 Next.js로 useragent 파싱시 조심해야 할 사항을 기록하기 위해 올립니다.

useragent를 이용한 웹뷰 작업이 필요하다

대부분의 웹뷰 작업 및 웹뷰를 감지하는 부분은 useragent에서 웹뷰를 감지하기 위한 useragent string을 파싱해서 사용합니다.

특히, SSR 기능을 사용하고 있는 Next.js에서는 serverSideProps에서 context의 req header의 user agent 를 이용해서, 앱인지 여부를 파싱한 후에 props에 내려줍니다. 물론 브라우저 (클라이언트) 에서 사용할 수도 있으나, 이렇게 하면 SSR을 이용하는 이유가 없어지고, 서버에서 웹뷰 여부에 따른 컴포넌트 렌더링 및, 이번 프로젝트에는 웹뷰에서만 띄워주어야 하는 페이지가 있었기 때문에, 모바일 웹에서 접근시에는 웹뷰 여부에 따라 다른 페이지로 redirect 혹은 다른 컴포넌트로 렌더링 하는 과정이 필요했습니다.

로컬은 되는데 배포만 하면 왜 안돼?

이 부분이 로컬에서는 정상적으로 작동하나, Amplify로 배포할때만 앱으로 파싱하지 못하고 있었습니다.

cloudwatch로 관련 로그를 살펴보니, Amplify에서 설정하고 있는 cloudfront의 url 동작 생성에 있어서, 기본값에 따른 캐시 정책이 다음과 같이 기본으로 설정되어 있습니다.

  • CloudFront-Is-Desktop-Viewer
  • CloudFront-Is-Mobile-Viewer
  • CloudFront-Is-SmartTV-Viewer
  • CloudFront-Is-Tablet-Viewer Useragent 헤더에 따라, cloudfront에서 자동으로 useragent를 파싱하며, 다음과 같은 헤더를 내려주고, CloudFront 자체에서는 해당 헤더로 변경하는 시점에 useragent를 다음과 같이 파싱해 줍니다.
User-Agent: Amazon CloudFront

황당했는데, 일단은 배포를 해야하고, SSR을 버릴 수는 없으니 해결 방안을 찾기 시작했습니다.

Amplify는 정작 이 이슈에 대해서는 unfortunately…

정작, 내가 원하는 header값이 유입이 되지 않는다는 issue에 amplify-hosting GitHub issue에서는 우리가 가장 듣기 싫어하는 단어.. unfortunately 가 뜹니다;;

해당 정책은 CloudFront 정책을 동일한 템플릿으로 사용하고 있어 변경이 불가능 하다는 것입니다.

물론, 변경은 가능합니다. Amplify에서 배포된 후 CloudFront 콘솔에 들어가 다음과 같이 찾습니다.

CloudFront 콘솔 > 배포 > 동작 > 기본값(*)

해당 부분으로 들어가면, 캐시 정책이 있는데, 캐시 정책에 설정되어 있는 Header중 위 Cloudfront에 대한 헤더를 제거하고, User-Agent 를 커스텀 헤더로 삽입하면 됩니다.

이때만 해도, 해결 방법을 알았다며 좋아했는데, 더큰 문제는 그 다음입니다..

배포 안하면 된다(?)

배포를 할때마다, Amplify에서는 Cloudfront의 기본 템플릿을 고대~~~로 복사해서 배포마다 덮어씌우는 형태로 배포를 진행합니다.

퐝당.. 내가 아무리 설정을 하더라도 배포를 덮어씌우는 겁니다. 이 부분은 이전에도 동일한 이슈가 있어 상당히 우려스럽긴 했습니다.. (예를 들면, 웹폰트에 대한 캐시 정책을 설정해도, 매번 배포때마다 무효화 되어 기본 설정값을 덮어씌우는 등)

결국에는, 다른 방법을 찾아야 했습니다.

이럴거면 sls-next를 써보자

동작 및 배포 방식은 동일합니다. sls-next를 사용하면, 실제로 cdn에 대한 접근은 cloudfront에서 처리해주고, SSR은 Lambda@Edge를 사용하며, 이외의 static file 및 빌드된 client 파일들은 S3를 사용하게 됩니다.

여기서 주요하게 봤던 것은, 물론 sls-next도 동일하게 cloudfront를 배포시마다 lambda 버전을 업데이트 하긴 하나, inputs 옵션을 통해 cloud front cache header 옵션을 사용할 수 있다는 것 이었습니다.

myNextApplication:
	component: '@sls-next/serverless-component'
	inputs:
		cloudfront:
			defaults:
				forward:
					headers:
						['User-Agent']

이내용을 본 순간.. 살았다는 생각이 먼저 듭니다. 물론, 이외에도 TTL 설정 등 cloudfront 운영에 필요한 기타 설정도 추가할 수 있습니다.

결론 - Next.js SSR쓸거면 Amplify가 프로덕션 레벨에서 이슈가 될수 있다

Amplify를 선택했던 이유는 배포에 대한 리소스를 최대한 줄이기 위함이 컸습니다. 다만, 프로덕션 레벨에서 진행하기에는 한계가 있을 수 있습니다. 주로, header 값을 이용하여 처리해야 하는 serverSideProps에는 Amplify의 설정이 쥐약일 수 있습니다. 헤더값들을 추가적으로 처리해주어야 하는데, 기본 default header로 계속 덮어씌워 지는거죠.

CloudFront 사용시에는 다음과 같은 방법으로 serverside에서 header를 활용할 수 있도록 만듭니다.

  • CloudFront 동작에서 캐시 관련 header에 User-Agent 를 사용함 (커스텀 헤더)
  • viewer request lambda function에서 별개의 헤더값으로 origin header를 복사하여 전달

정말 별거 다한듯 합니다. 실제로 origin 서버로 request 하기 전, viewer request용으로 lambda를 만들어서 사용도 해보았으나, 늘 초기화 되는 이슈때문에 좌절을 겪습니다. 다른 분들도 이런 황당한 경험을 겪는것을 최소화 하기 위해서, 이 포스팅을 남깁니다. 🙇‍♂️

...