o2o-infinith-demo/vite.config.ts
Haewon Kam f03c6a5b90 feat: webforai.kr 공개 전 정리 (비식별 샘플·실제 리포트 비공개·약관)
- 랜딩 샘플 리포트·AI 답변 예시·문의 서식 예시를 가상 A성형외과로 바꾼다 (discovery_sample, 고정 시드 무작위 판정)
- VITE_SITE=discovery 배포는 랜딩·요금·로그인·약관만 연다. 실제 병원 진단 결과와 내부 화면 경로는 홈으로 돌린다
- src/data·src/services 를 부수효과 없는 모듈로 표시해 쓰지 않는 실제 병원 목업이 공개 번들에서 빠지게 한다
- 공개 배포 번들에 GEMINI_API_KEY 를 넣지 않는다
- 헤더 Product·Use Cases 는 공개 배포에서 숨기고, 푸터에 개인정보처리방침·이용약관 페이지를 연결한다
- 기준표 설명의 실제 병원 예시 문구를 일반 예시로 바꾼다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:23:41 +09:00

115 lines
5.5 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: {
// 공개 배포(webforai.kr)에는 이미지 생성 기능이 없으므로 키를 번들에 넣지 않는다. 브라우저로 내려간 값은 공개다.
'process.env.GEMINI_API_KEY': JSON.stringify(isDiscovery ? '' : env.GEMINI_API_KEY),
},
build: {
rollupOptions: {
treeshake: {
// src/data(목업·진단 데이터)와 src/services 는 import 만으로 할 일이 없는 모듈이다. 쓰는 곳이 없으면 번들에서 뺀다.
// 파생 목업의 최상단 객체 펼치기를 부수효과로 보고 실제 병원 목업이 공개 번들에 남던 문제를 막는다.
moduleSideEffects: (id: string) => !/[\\/]src[\\/](data|services)[\\/]/.test(id),
},
},
},
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',
},
};
});