페이지 커버 썸네일 최적화

This commit is contained in:
2026-07-02 10:09:39 +09:00
parent b26ae271d2
commit 3a4e39332a
20 changed files with 463 additions and 25 deletions

View File

@@ -3,17 +3,21 @@ import { mkdir, stat } from 'node:fs/promises'
import { dirname, extname, join, posix } from 'node:path'
import sharp from 'sharp'
import {
getPageCoverThumbnailUrl,
getPostBodyPreviewThumbnailUrl,
getPostBodyThumbnailUrl,
getPostLightboxThumbnailUrl,
isPageCoverThumbnailUrl,
isPostBodyThumbnailUrl,
isPostCardThumbnailUrl
} from '../../lib/post-card-thumbnail-url.js'
export {
getPageCoverThumbnailUrl,
getPostBodyPreviewThumbnailUrl,
getPostBodyThumbnailUrl,
getPostLightboxThumbnailUrl,
isPageCoverThumbnailUrl,
isPostBodyThumbnailUrl,
isPostCardThumbnailUrl
}
@@ -25,6 +29,9 @@ export const POST_BODY_IMAGE_WIDTH = 1600
export const POST_BODY_PREVIEW_IMAGE_WIDTH = 720
export const POST_LIGHTBOX_IMAGE_WIDTH = 2560
export const POST_BODY_IMAGE_QUALITY = 84
export const PAGE_COVER_THUMBNAIL_WIDTH = 450
export const PAGE_COVER_THUMBNAIL_HEIGHT = 600
export const PAGE_COVER_THUMBNAIL_QUALITY = 84
const postUploadUrlPattern = /^\/uploads\/posts\/\d{4}\/\d{2}\/[^/?#]+$/i
const postUploadImageUrlPattern = /\/uploads\/posts\/\d{4}\/\d{2}\/[^/\s"'()<>\]]+\.(?:jpe?g|png|webp)/gi
@@ -154,6 +161,21 @@ export const getPostLightboxThumbnailDiskPath = (imageUrl) => {
return join(process.cwd(), 'public', decodeUrlPathPart(thumbnailUrl.replace(/^\/+/, '')))
}
/**
* 페이지 커버 썸네일 URL의 디스크 경로를 조회한다.
* @param {string|null|undefined} imageUrl - 원본 이미지 URL
* @returns {string} 페이지 커버 썸네일 디스크 경로
*/
export const getPageCoverThumbnailDiskPath = (imageUrl) => {
const thumbnailUrl = getPageCoverThumbnailUrl(imageUrl)
if (!thumbnailUrl) {
return ''
}
return join(process.cwd(), 'public', decodeUrlPathPart(thumbnailUrl.replace(/^\/+/, '')))
}
/**
* 게시물 원본 이미지 URL의 디스크 경로를 조회한다.
* @param {string|null|undefined} imageUrl - 원본 이미지 URL
@@ -245,6 +267,21 @@ export const getExistingPostLightboxThumbnailUrl = (imageUrl) => {
return existsSync(getPostLightboxThumbnailDiskPath(imageUrl)) ? thumbnailUrl : ''
}
/**
* 이미 생성된 페이지 커버 썸네일 URL을 조회한다.
* @param {string|null|undefined} imageUrl - 원본 이미지 URL
* @returns {string} 존재하는 페이지 커버 썸네일 URL
*/
export const getExistingPageCoverThumbnailUrl = (imageUrl) => {
const thumbnailUrl = getPageCoverThumbnailUrl(imageUrl)
if (!thumbnailUrl) {
return ''
}
return existsSync(getPageCoverThumbnailDiskPath(imageUrl)) ? thumbnailUrl : ''
}
/**
* 게시물 대표 이미지의 카드 썸네일을 생성하거나 다시 생성한다.
* @param {string|null|undefined} imageUrl - 원본 이미지 URL
@@ -282,6 +319,43 @@ export const createPostThumbnailForImageUrl = async (imageUrl) => {
return thumbnailUrl
}
/**
* 페이지 홈 진열장용 3:4 커버 썸네일을 생성하거나 다시 생성한다.
* @param {string|null|undefined} imageUrl - 원본 이미지 URL
* @returns {Promise<string>} 생성된 페이지 커버 썸네일 URL
*/
export const createPageCoverThumbnailForImageUrl = async (imageUrl) => {
const sourcePath = getPostImageDiskPath(imageUrl)
const thumbnailUrl = getPageCoverThumbnailUrl(imageUrl)
const thumbnailPath = getPageCoverThumbnailDiskPath(imageUrl)
if (!sourcePath || !thumbnailUrl || !thumbnailPath) {
return ''
}
try {
await stat(sourcePath)
} catch {
return ''
}
await mkdir(dirname(thumbnailPath), { recursive: true })
await sharp(sourcePath)
.rotate()
.resize({
width: PAGE_COVER_THUMBNAIL_WIDTH,
height: PAGE_COVER_THUMBNAIL_HEIGHT,
fit: 'cover',
position: 'centre',
withoutEnlargement: true
})
.webp({ quality: PAGE_COVER_THUMBNAIL_QUALITY })
.toFile(thumbnailPath)
return thumbnailUrl
}
/**
* 게시물 본문 이미지의 표시용 WebP 이미지를 생성하거나 다시 생성한다.
* @param {string|null|undefined} imageUrl - 원본 이미지 URL