개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← YouTube Shorts 자동화

성능 개선 — SSR prefetch와 썸네일 단순화

2026년 7월 20일
webrender-workerapi성능

홈 화면이 뜨고 나서 Jobs 목록이 한 박자 늦게 채워지는 게 계속 거슬렸다. 오늘은 그거랑 겹쳐 있던 문제들을 같이 정리했다.

홈 — SSR prefetch

page.tsx(서버 컴포넌트)에서 channels만 조회하고 Jobs는 클라이언트에서 useQuery로 다시 불러오고 있었다. 서버에서 첫 채널의 Jobs까지 미리 가져와서 넘겨주도록 바꿨다.

const firstChannelId = channels[0]?.id ?? '';
const initialJobs = firstChannelId
  ? await apiGet<Job[]>(`/jobs?channelId=${firstChannelId}`, userHeaders).catch(() => [])
  : [];
return <HomeClient channels={channels} userId={userId} firstChannelId={firstChannelId} initialJobs={initialJobs} />;

useQuery는 activeChannelId가 firstChannelId랑 같을 때만 initialData를 쓴다. 다른 채널로 넘어가면 그냥 클라이언트에서 fetch한다.

initialData: activeChannelId === firstChannelId ? initialJobs : undefined,

render-worker — S3 썸네일 생성 제거

썸네일을 render-worker가 FFmpeg -vframes 1로 첫 프레임을 뽑아서 S3에 올리고 있었는데, upload-worker가 YouTube 업로드 완료 시 youtubeVideoId를 저장하기 시작하면서 i.ytimg.com CDN을 그냥 쓰면 되는 상태가 됐다. 같은 정보를 두 번 만드는 셈이라 S3 경로를 통째로 뺐다.

-const thumbnailPath = join(tmpDir, `${jobId}-thumbnail.jpg`);
-concatClipsWithAudio(clipPaths, audioPath, srtPath, outputPath, ..., thumbnailPath);
+concatClipsWithAudio(clipPaths, audioPath, srtPath, outputPath, ...);

FFmpeg 커맨드 자체도 렌더러에서 걷어냈다.

-if (thumbnailPath) {
-  try {
-    execSync(`"${ffmpegPath}" -y -i "${concatPath}" -vf "${headerFilter},${footerFilter}" -vframes 1 -q:v 2 "${thumbnailPath}"`, { stdio: 'pipe' });
-  } catch { /* 썸네일 생성 실패 시 무시 */ }
-}

프론트에서는 youtubeVideoId 유무로 어떤 썸네일을 쓸지 고르는 헬퍼 하나로 통합했다.

export function effectiveThumbUrl(youtubeVideoId: string | null | undefined, thumbnailUrl: string | null | undefined): string | null {
  if (youtubeVideoId) return `https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`;
  return toProxyThumbUrl(thumbnailUrl);
}

Channel — totalViews 부분 합산 덮어쓰기 제거

syncChannel이 YouTube Data API로 채널 전체 누적 조회수를 정확히 저장하는데, 바로 뒤에서 syncVideos가 앱에서 업로드한 영상들의 viewCount만 합산해서 같은 필드를 또 덮어쓰고 있었다. 당연히 YouTube Studio 수치랑 어긋난다.

-const totalVideoViews = viewCountUpdates.reduce((sum, v) => sum + v.viewCount, 0);
-await this.repo.updateTotalViews(channelId, totalVideoViews);

updateTotalViews 메서드 자체를 지웠다. 이제 totalViews는 syncChannel 한 곳에서만 쓴다.

같이 syncChannel에서 채널명도 갱신하도록 살짝 얹었다. channels.list에 snippet part를 추가하면 되는 간단한 거였다.

const channelRes = await yt.channels.list({ part: ['statistics', 'snippet'], id: [channelRow.youtubeId] });
← 이전 글썸네일 프록시 — 안정화와 일일 생성 한도
다음 글 →스크립트 품질 — 종결어 중복 방지와 TTS 롤백