..

url parameter is not allowed

개요

Next.js 14.2.5 환경에서 외부 S3 버킷의 이미지를 Next.js Image 컴포넌트로 불러올 때 발생하는 “url parameter is not allowed” 400 에러에 대한 해결 방법을 다룹니다.

Next.js의 Image Optimization API는 이미지를 자동으로 최적화(리사이징, 포맷 변환, 품질 조정 등)하여 성능을 향상시키는 기능이지만, 보안상의 이유로 next.config.jsimages.domains 또는 images.remotePatterns에 명시적으로 허용된 외부 도메인만 처리할 수 있습니다.

이 문제는 허용되지 않은 외부 도메인의 이미지 URL을 Image Optimization API가 거부하여 발생하며, 커스텀 loader를 통해 최적화 과정을 우회함으로써 해결할 수 있습니다.

증상

앞단의 Cloudflare는 통과했으며 에셋용 S3는 CORS 설정이 아예 없으나 400 Bad Request가 발생함.

로컬에서 해당 프론트엔드에 노출된 URL의 Image Optimization API를 호출해봅니다.

$ curl -v "https://my.frontend.co.kr/_next/image?url=https%3A%2F%2F<REDACTED_BUCKET_URL>%2F<ASSET_NAME>.png&w=1080&q=75"
...
< HTTP/2 400
< date: Tue, 12 Aug 2025 06:47:40 GMT
< cf-ray: 96de05cb18e58b5f-ICN
< cf-cache-status: DYNAMIC
< server: cloudflare
<
* Connection #0 to host 
"url" parameter is not allowed

해당 에러의 반환은 image-optimizer.ts 코드에서 직접 확인 가능합니다.

프론트엔드 파드가 참조하는 해당 버킷의 이미지 에셋 URL에 직접 들어가면 이미지가 정상적으로 출력되며, 200 OK 응답 반환되는 것을 확인

환경

  • Kubernetes 1.32
  • Next.js 14.2.5
$ cat package.json | grep next
    "@mui/material-nextjs": "^5.15.11",
    "next": "14.2.5",

해결방법

Next.js 서버 설정파일인 next.config.js에 remotePatterns 값을 확인합니다.

const config = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "**.first-bucket.co.kr",
        port: "",
        pathname: "**",
      },
      {
        protocol: "https",
        hostname: "**.second-bucket.co.kr",
        port: "",
        pathname: "**",
      },
    ],
  },
  assetPrefix,
  crossOrigin: "anonymous"
};

버킷 URL은 remotePatterns에 잘 등록되어 있습니다.

관련 이미지를 불러오는 .tsx 파일에서 loader를 추가합니다. 레포지터리에서의 모노레포 절대경로는 /apps/<REDACTED>/app/<REDACTED>/(home)/_module/components/banner/banner.tsx 이런식입니다.

수정 전:

  return (
    <Root>
      <SlideView
        {...STAKING_BANNER_SLIDE_VIEW_SETTINGS}
        dots={!isSingleBanner}
        infinite={!isSingleBanner}
        autoplay={!isSingleBanner}
      >
        {data?.banners.map(
          (banner) =>
            banner.lightBannerImagePath && (
              <Button
                key={`banner-${banner.id}`}
                onMouseDown={handleMouseDown}
                onClick={(e) => handleOnClick(e, moveToBannerLink(banner))}
              >
                <BannerImage
                  src={
                    isLight(theme)
                      ? banner.lightBannerImagePath
                      : banner.darkBannerImagePath
                  }
                  alt="staking banner"
                  width={480}
                  height={109}
                />
              </Button>
            ),
        )}
      </SlideView>
    </Root>
  );

수정 후:

배너 이미지를 호출하는 .tsx 파일에서 loader를 추가하니까 해결됨

                <BannerImage
                  loader={() =>
                    isLight(theme)
                      ? banner.lightBannerImagePath
                      : banner.darkBannerImagePath
                  }
                  src={
                    isLight(theme)
                      ? banner.lightBannerImagePath
                      : banner.darkBannerImagePath
                  }
                  alt="staking banner"
                  width={480}
                  height={109}
                />

동작 원리

여러 이유들로 인해 설정파일(next.config.js)에 remotePatterns가 설정되어 있어도 여전히 에러가 발생할 수 있으며, 프론트엔드 코드 단에서 커스텀 loader를 사용하는 것이 더 확실한 해결책이 될 수 있습니다.

  • 기본 동작: Next.js Image 컴포넌트는 src 속성의 URL을 /_next/image?url=<encoded_url>&w=&q= 형태로 변환하여 Image Optimization API를 통해 처리
  • 커스텀 loader 적용: loader 함수가 제공되면 Image Optimization API를 우회하고 loader 함수가 반환하는 URL을 직접 사용
  • 결과: 외부 S3 버킷의 이미지가 remotePatterns 설정 없이도 직접 로드됨

이 방법을 사용하면 Next.js의 이미지 최적화 기능(자동 리사이징, WebP 변환 등)은 사용할 수 없지만, 외부 도메인 제한 문제를 해결할 수 있습니다.

관련자료