next/image
next.js에서 이미지를 최적화하기 위해서는 next.js에서 제공하는 next/image api를 사용하는 것이 일반적으로는 최적이다.
// 인라인 문자열 방식
<Image src={'/public/image/backgounrd.png'} alt='background' />
// Static Image Import(정적 임포트 방식)
import backgroundImg from '@/image/backgounrd.png';
<Image src={backgroundImg} alt='background' />
next.js 공식문서에서는 Static Image Import 방식으로 이미지를 임포트하는 것을 권장한다. 임포트 방식에 따라 이미지 헤더에 붙일 Cache-Control을 내부적으로 제어하기 때문이다.
Static Image Import
Nextjs 공식 문서 - https://nextjs.org/docs/app/api-reference/components/image#minimumcachettl
nextjs는 빌드 시에 webpack을 사용하는데, 이미지를 정적으로 임포트했기 때문에 webpack에서 이미지가 사용된 컴포넌트를 파악할 수 있다. 이때 Static Image Import된 이미지는 빌드 시 .next/static/media/
폴더 아래에 파일명이 해싱된다.
[name].[hash:8][ext]
이 해시값은 이미지 데이터를 해싱해서 생성되었기 때문에 파일이 변경되지 않는 이상 동일한 값을 가진다.(콘텐츠 기반 해싱)
next.js webpack 설정
- next.js/package/next/src/build/webpack-config.ts
- nextjs 패키지의 기본 webpack 설정이다.
- 프로젝트 빌드 시 static/media/[name].[hash:8][ext] 의 파일명을 가지도록 한다.
webpack5Config.module!.generator = {
asset: {
filename: 'static/media/[name].[hash:8][ext]',
},
}
next.js image cache
그리고 next.js에서는 이미지 임포트 방식에 따라 이미지 캐싱 전략(Cache-Control)을 다르게 처리한다. Static Import Image의 경우 'public, max-age=315360000, immutable'이고, 인라인 문자열의 경우 public, max-age=0, must-revalidate
이다. 인라인 문자열 임포트의 경우 사실상의 캐싱이 무효화되는 셈이다.
- next.js/packages/next/src/server/image-optimizer.ts
- next 서버에서 이미지 요청 시 호출하는 헤더구성 함수
- isStatic: 해당 이미지가 정적으로 임포트된 이미지인지 여부(boolean)모던 브라우저에서는 동일한 이미지 명에 대해서는 캐싱을 수행한다.
첫 번째 요청 시에는 파일을 받아오고 두 번째 요청부터 이미지 헤더의 Cache-Control을 보고 캐싱 전략을 수행한다. 이때 파일명이 같지만 다른 이미지로 교체하는 일이 있을 때, Static Import Image는 파일명 해싱이 이루어지기 때문에 해시값이 달라져서 다른 이미지 파일임을 알고 캐싱을 무효화할 테지만, 인라인 문자열 임포트는 파일명은 동일하기 때문에 이미지 파일이 변경되었어도 브라우저에서는 이를 알 수 없다. 이 때문에 next.js에서는 인라인 문자열 임포트의 경우 'must-revalidate'를 붙인게 아닐까 추측해본다.
function setResponseHeaders(
req: IncomingMessage,
res: ServerResponse,
url: string,
etag: string,
contentType: string | null,
isStatic: boolean,
xCache: XCacheHeader,
imagesConfig: ImageConfigComplete,
maxAge: number,
isDev: boolean
) {
res.setHeader('Vary', 'Accept')
res.setHeader(
'Cache-Control',
isStatic
? 'public, max-age=315360000, immutable'
: `public, max-age=${isDev ? 0 : maxAge}, must-revalidate`
)
if (sendEtagResponse(req, res, etag)) {
// already called res.end() so we're finished
return { finished: true }
}
if (contentType) {
res.setHeader('Content-Type', contentType)
}
const fileName = getFileNameWithExtension(url, contentType)
res.setHeader(
'Content-Disposition',
contentDisposition(fileName, { type: imagesConfig.contentDispositionType })
)
res.setHeader('Content-Security-Policy', imagesConfig.contentSecurityPolicy)
res.setHeader('X-Nextjs-Cache', xCache)
return { finished: false }
}
'IT > Front-End' 카테고리의 다른 글
[리팩토링 2판] 7장 캡슐화 (1) | 2024.11.03 |
---|---|
리팩토링 - 장대한 시작.. (0) | 2024.10.15 |
[Next.js] ERR_HTTP_HEADERS_SENT 응답 헤더 중복 에러 (0) | 2024.09.28 |
[FE] xlsx 취약점 버전 업데이트 (0) | 2024.08.06 |
[FE] 우리에게 적절한 방법론이란 뭘까 (0) | 2024.08.06 |