o2o-infinith-demo/vite.config.ts
Haewon Kam d1c846b6aa fix(front): 루트 /favicon.ico 를 discovery 배포에 넣는다
미리보기 카드와 일부 브라우저는 <link rel="icon"> 을 보지 않고 /favicon.ico 를
그대로 요청한다. vercel.json 의 SPA 리라이트가 모든 경로를 index.html 로 보내서
아이콘 자리에 HTML(text/html 2,589B)이 도착했고, 공유 카드에 회색 링크 아이콘이 나왔다.

public/ 은 infinith-demo 배포와 공용이라 파일명을 discovery- 로 나눠 두었으므로,
빌드 때 이 배포의 아이콘만 루트 이름으로 복사한다. infinith-demo 빌드는 그대로다.

검증: discovery 빌드에 favicon.ico(16·32·48)·favicon-32·192·apple-touch-icon 생성,
기본 빌드에는 생기지 않음. tsc 0 에러.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 09:27:10 +09:00

105 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import fs from 'fs';
import path from 'path';
import {defineConfig, loadEnv} from 'vite';
import { supabaseSync } from './plugins/vite-plugin-supabase-sync';
export default defineConfig(({mode}) => {
const env = loadEnv(mode, '.', '');
// 배포별 head 메타. VITE_SITE=discovery 로 빌드한 배포(webforai.kr)는 AI Discovery 랜딩이 루트라
// 제목·설명도 그에 맞아야 한다. index.html 은 두 배포가 공용이므로 빌드 때 갈아끼운다.
// 답변엔진이 읽는 첫 신호라 비워 두지 않는다.
const isDiscovery = env.VITE_SITE === 'discovery';
const head = isDiscovery
? {
title: 'AI Discovery · 우리 병원은 AI 답변에 나오고 있습니까',
description: '병원 홈페이지 주소 하나로 AI 답변 준비도를 진단하고, 무엇을 고쳐야 하는지 근거와 함께 알려 드립니다.',
}
: {
title: 'INFINITH - Infinite Marketing',
description: 'AI 마케팅 분석 플랫폼. 채널을 진단하고 전략과 로드맵을 제안합니다.',
};
// 검색엔진 소유 확인. 배포마다 값이 다르므로 여기서 갈아끼운다.
// 네이버 서치어드바이저는 DNS 를 받지 않아 메타 태그로만 확인된다.
// 구글은 webforai.kr 도메인 속성을 DNS TXT 로 확인했으므로 메타가 필요 없다.
const verify = isDiscovery
? ['<meta name="naver-site-verification" content="6ebcb18640acf31bb3653822d178fcf071b94009" />']
: [];
// 링크 공유 미리보기 이미지와 파비콘(scripts/make_share_assets.py discovery 로 생성).
// og:image 는 절대 주소여야 카카오톡·슬랙이 읽는다. public/ 은 infinith-demo 와 공용이라 discovery 빌드에만 넣는다.
const share = isDiscovery
? [
'<meta property="og:url" content="https://webforai.kr" />',
'<meta property="og:image" content="https://webforai.kr/og-discovery.jpg" />',
'<meta property="og:image:width" content="1200" />',
'<meta property="og:image:height" content="630" />',
'<meta name="twitter:card" content="summary_large_image" />',
'<link rel="icon" href="/discovery-favicon.ico" sizes="48x48" />',
'<link rel="icon" type="image/png" sizes="32x32" href="/discovery-favicon-32.png" />',
'<link rel="icon" type="image/png" sizes="192x192" href="/discovery-favicon-192.png" />',
'<link rel="apple-touch-icon" href="/discovery-apple-touch-icon.png" />',
]
: [];
const headHtml = [
`<title>${head.title}</title>`,
`<meta name="description" content="${head.description}" />`,
`<meta property="og:type" content="website" />`,
`<meta property="og:title" content="${head.title}" />`,
`<meta property="og:description" content="${head.description}" />`,
`<meta property="og:locale" content="ko_KR" />`,
...share,
...verify,
].join('\n ');
return {
plugins: [
react(),
{
// 루트 /favicon.ico. 미리보기 카드와 일부 브라우저는 <link rel="icon"> 을 보지 않고 루트를 그대로 요청한다.
// vercel.json 의 SPA 리라이트가 모든 경로를 index.html 로 보내므로, 파일이 없으면 HTML 이 아이콘 자리에 온다.
// public/ 은 infinith-demo 배포와 공용이라 파일명을 나눠 두었고, 빌드 때 이 배포의 것만 루트 이름으로 복사한다.
name: 'infinith-root-favicon',
apply: 'build' as const,
writeBundle(options: {dir?: string}) {
if (!isDiscovery) return;
const out = options.dir ?? 'dist';
for (const [from, to] of [
['discovery-favicon.ico', 'favicon.ico'],
['discovery-favicon-32.png', 'favicon-32.png'],
['discovery-favicon-192.png', 'favicon-192.png'],
['discovery-apple-touch-icon.png', 'apple-touch-icon.png'],
]) {
const src = path.resolve(out, from);
if (fs.existsSync(src)) fs.copyFileSync(src, path.resolve(out, to));
}
},
},
{
name: 'infinith-head',
transformIndexHtml(html: string) {
return html.replace(/<title>[\s\S]*?<\/title>/, headHtml);
},
},
tailwindcss(),
supabaseSync({
supabaseUrl: env.VITE_SUPABASE_URL ?? '',
serviceRoleKey: env.SUPABASE_SERVICE_ROLE_KEY ?? env.VITE_SUPABASE_ANON_KEY ?? '',
}),
],
define: {
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
server: {
// HMR is disabled in AI Studio via DISABLE_HMR env var.
// Do not modify—file watching is disabled to prevent flickering during agent edits.
hmr: process.env.DISABLE_HMR !== 'true',
},
};
});