manifest.json 파일을 손으로 고치다가 오탈자 하나 때문에 PWA 설치가 이상하게 됐던 적이 있어서, 그 뒤로는 타입으로 검증되는 방식을 선호하게 됐어요. img-compressor 저장소의 app/manifest.ts, app/robots.ts는 정적 파일이 아니라 함수로 짜여있어요.
manifest.ts: PWA 설치 정보를 타입으로 검증해요
import { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'PixelZipKit XP Image Compressor - Free Image Compression and WebP Conversion',
short_name: 'PixelZipKit',
description: 'Compress images and convert them to WebP directly in your browser.',
start_url: '/ko',
display: 'standalone',
background_color: '#f8f7ef',
theme_color: '#245edb',
icons: [
{ src: '/icon-192x192', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512x512', sizes: '512x512', type: 'image/png' },
],
};
}
MetadataRoute.Manifest 타입을 반환 타입으로 딱 지정해두면, display에 standalone 아닌 이상한 문자열을 넣거나 icons 필드명을 틀렸을 때 빌드 시점에 바로 타입 오류로 잡혀요. 정적 manifest.json이었으면 이런 오탈자는 실제 기기에 설치해보기 전까진 몰랐을 거예요.
robots.ts: 크롤링 규칙이랑 사이트맵 경로를 같이 선언해요
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{ userAgent: "*", allow: "/", disallow: ["/api/"] },
],
sitemap: \`\${baseUrl}/sitemap.xml\`,
host: baseUrl,
};
}
disallow: ["/api/"]는 서버 라우트를 검색 엔진이 못 긁게 막는, 나름 확실한 이유가 있는 규칙이에요. API 응답은 사람이 읽는 페이지가 아니고, 크롤러가 이 경로를 계속 호출하면 서버 부하만 쓸데없이 늘어나거든요. baseUrl은 lib/site-url.ts의 getSiteUrl() 한 함수에서 가져와서, 배포 도메인이 바뀌어도 이 파일을 직접 고칠 필요가 없어요.
정적 파일 대신 함수로 관리하는 게 좋은 이유는 재사용성이에요. baseUrl이나 locales 같은 값을 여러 메타데이터 파일이 같은 곳에서 가져오니까, 값 하나 바꾸면 매니페스트·robots·사이트맵이 한 번에 갱신돼요. 다만 이 이점은 프로젝트가 실제로 여러 메타데이터 파일에서 같은 값을 공유할 때만 체감되고, 정적 파일 하나짜리 작은 사이트에서는 큰 차이가 없더라고요.