Synapse 구축: React Native와 Supabase를 활용한 전체 기능을 갖춘 소셜 미디어 앱 (곧 출시!)
I’m ready to translate the article for you, but I need the full text you’d like translated. Could you please provide the content (excluding the source line you already shared)? Once I have it, I’ll keep the source link at the top and translate the rest into Korean while preserving all formatting, markdown, and technical terms.
주요 기능
- 다중 블록 게시물 (텍스트, 이미지, 비디오, 오디오, 파일, 라이브 포토)
- 구문 강조와 MathJax 지원이 포함된 마크다운
- 피드 추천
- 실시간 직접 메시징
- 전체 텍스트 검색
왜 우리가 이것을 만들었는가
Synapse의 아이디어는 많은 연구자와 개발자들이 공유하는 좌절감에서 비롯되었습니다: 기존 소셜 플랫폼은 기술적 담론을 위해 설계되지 않았습니다.
우리가 계속 마주치는 문제들
1. 파편화된 토론
X(트위터)에서 머신러닝 연구 토론을 따라가 보세요. 하나의 논문 분석이 15개 이상의 트윗으로 나뉘고, 방정식은 스크린샷으로, 코드는 이미지로 제공됩니다. 인용 트윗과 답글 속에서 맥락이 사라집니다. 혼란 그 자체입니다.
2. 풍부한 콘텐츠에 대한 내장 지원 부족
논문 PDF를 공유하고 싶나요? 코드에 대한 오디오 설명을 업로드하고 싶나요? 데이터셋 파일을 첨부하고 싶나요? 현재 플랫폼은 모든 것을 텍스트 + 이미지/동영상으로만 취급해, 기본 미디어를 넘어서는 모든 것을 외부 서비스에 링크하도록 강요합니다.
3. 발견(Discovery)이 망가졌다
자신의 니치에 맞는 적절한 사람들을 찾는 것이 생각보다 어렵습니다. 알고리즘은 참여도를 최적화할 뿐, 관련성을 고려하지 않습니다. 특정 하위 분야의 연구자나 유사한 문제를 다루는 개발자를 발견할 좋은 방법이 없습니다.
비전
우리는 다음을 포함하는 단일하고 일관된 게시물을 작성할 수 있는 플랫폼을 원했습니다:
- 적절한 서식이 적용된 마크다운 설명
- 아름답게 렌더링되는 수학 방정식 (LaTeX/MathJax)
- 구문 강조가 적용된 코드 스니펫
- 실제 PDF 또는 데이터셋 파일
- 듣는 것을 선호하는 사람들을 위한 오디오 워크스루
한 곳에 모두. 스레드 없음. 외부 링크 없음. 방정식 스크린샷 없음.
이 대상은 누구인가요?
연구자 및 학계
자신의 연구를 공유하고, 논문을 토론하며, 분야 내 동료들과 연결하고자 하는 과학자들. 머신러닝, 물리학, 생물학, 혹은 어떤 기술 분야에 있든, 여러분은 여러분의 언어를 그대로 지원하는 플랫폼—즉, 기본 LaTeX 지원을 갖춘 플랫폼을 누릴 자격이 있습니다.
개발자 및 기술 창작자
아름답고 읽기 쉬운 형식으로 아이디어, 튜토리얼, 프로젝트를 공유하고자 하는 프로그래머들. 적절한 코드 하이라이팅으로 기술 콘텐츠를 작성하고, 관련 파일을 첨부하며, 바이럴보다 실질을 중시하는 청중을 구축하세요.
우리는 최고의 아이디어가 문자 수 제한이나 파편화된 생각의 스레드보다 더 나은 대우를 받아야 한다고 믿습니다.
Source: …
기술 스택
프론트엔드
- React Native 0.81 (새 아키텍처 활성화)
- Expo SDK 54 (빠른 개발 및 OTA 업데이트)
- React 19 (최신 동시성 기능)
- TypeScript (strict 모드,
any금지)
상태 관리
하이브리드 접근 방식을 채택했습니다:
- React Query 5 (서버 상태 및 캐싱)
- Zustand (클라이언트‑사이드 스토어)
- Context API (전역 앱 상태: Auth, Audio, Analytics)
백엔드
Supabase 를 Backend‑as‑Service 로 사용
- PostgreSQL (데이터베이스)
- Supabase Auth (이메일 OTP)
- Supabase Storage (S3‑호환) (미디어)
- Realtime subscriptions (실시간 업데이트)
- Edge Functions (Deno) (서버리스 컴퓨팅)
Source: …
기술 심층 탐구
1. 콘텐츠 블록 시스템
게시물을 평문 텍스트로 저장하는 대신, 유연한 블록 기반 아키텍처를 설계했습니다:
type ContentBlock = TextBlock | MediaBlock | AudioBlock | FileBlock;
interface MediaBlock {
type: 'media';
items: MediaItem[];
}
interface MediaItem {
type: 'image' | 'video' | 'live_photo';
storagePath: string;
width: number;
height: number;
thumbnailUrl?: string;
// Live photos have separate video/photo URLs
videoStoragePath?: string;
}
왜 중요한가
- 게시물에 혼합된 콘텐츠 유형을 포함할 수 있음
- 각 블록이 자체 렌더링 로직을 담당
- 향후 새로운 블록 유형을 쉽게 추가 가능
- 런타임 타입 가드와 함께 타입‑안전 보장
2. 낙관적 메시징
서버 응답을 기다리면서 메시지를 보는 사람은 없습니다. 우리는 로컬 ID를 사용한 낙관적 업데이트를 구현했습니다:
// Generate local ID for immediate display
const localId = `local_${Date.now()}_${Math.random().toString(36)}`;
// Show message immediately with "pending" status
addMessageToCache({
id: localId,
content,
status: 'pending',
created_at: new Date().toISOString(),
});
// Server confirms and provides real ID
// (replace the local entry with the server‑provided one)
const { data } = await supabase
.from('messages')
.insert({ content, conversation_id })
.select()
.single();
// Reconcile local and server states
replaceLocalMessage(localId, data);
어려운 부분? 엣지 케이스 처리
- 서버 응답이 낙관적 업데이트보다 먼저 도착하면 어떻게 할까?
- 사용자가 메시지를 빠르게 여러 개 보낼 경우는?
- 실패를 어떻게 우아하게 처리할까?
우리는 이러한 시나리오를 모두 다루는 OptimisticMessageManager를 구축했고, 적절한 조정 로직을 구현했습니다.
3. 스마트 캐싱을 이용한 서명된 URL
Supabase Storage의 미디어 파일은 비공개 접근을 위해 서명된 URL이 필요합니다. 문제는 이 URL이 만료된다는 점입니다!
우리의 해결책
- TTL을 추적하며 서명된 URL을 캐시
- 만료 5분 전에 URL을 갱신
- API 호출을 줄이기 위해 URL 요청을 배치
class SignedUrlService {
private cache = new Map<string, { url: string; expiresAt: number }>();
async getSignedUrl(storagePath: string, postId?: string): Promise<string> {
const cacheKey = this.buildCacheKey(storagePath, postId);
const cached = this.cache.get(cacheKey);
if (cached && !this.isExpiringSoon(cached.expiresAt)) {
return cached.url;
}
return this.fetchAndCache(storagePath, postId);
}
// Helper methods (buildCacheKey, isExpiringSoon, fetchAndCache) omitted for brevity
}
캐시 무효화를 위해 postId 컨텍스트를 전달합니다.
4. 피드 추천 엔진
우리는 서버‑사이드 추천 RPC를 구축했으며, 여러 요소를 사용해 게시물에 점수를 매깁니다:
- 최신성
- 참여 지표
- 사용자의 소셜 그래프
- 콘텐츠 유형 선호도
// Edge Function: feed-recommendation
const candidates = await fetchCandidates(userId, excludeIds);
const scored = candidates.map(post => ({
...post,
score: calculateRelevanceScore(post, userPreferences)
}));
return scored
.sort((a, b) => b.score - a.score)
.slice(0, limit);
사용자는 연대순 피드와 추천 피드 사이를 전환할 수 있습니다.
5. 제스처 감지를 이용한 오디오 녹음
우리는 커스텀 HoldToRecordButton 및 HoldToRecordOverlay 컴포넌트를 만들었습니다:
- 길게 누르면 녹음 시작
- 취소 영역으로 드래그하면 녹음 중단
- 시각적인 파형 피드백 제공
- 상태 변화 시 햅틱 피드백
이를 위해 React Native Gesture Handler와 Expo Audio와의 깊은 통합이 필요했습니다.
배운 교훈
1. TypeScript 엄격 모드로 시작하기
우리는 첫날부터 엄격 모드를 활성화했습니다. 그렇지 않으면 런타임 오류가 될 수 있는 수많은 버그를 개발 단계에서 잡아냈습니다. 초기 투자가 큰 수익을 가져옵니다.
2. 서버‑사이드 로직 > 클라이언트‑사이드 복잡성
클라이언트에서 복잡한 상태를 관리하는 대신(예: 사용자를 차단할 때 연쇄 삭제), 데이터베이스 트리거를 사용합니다:
CREATE TRIGGER on_user_blocked
AFTER INSERT ON blocked_users
FOR EACH ROW
EXECUTE FUNCTION cleanup_follows_on_block();
이렇게 하면 클라이언트가 작업 중에 충돌하더라도 데이터 일관성을 보장합니다.
3. 오프라인 우선 설계
우리는 MMKV(빠른 키‑값 저장소)를 사용해 TTL이 있는 로컬 캐시를 구현합니다:
- 프로필: 5 일
- 피드 데이터: 2 일
- 메시지: 대기 중인 상태와 동기화
대부분의 데이터가 캐시에서 제공되기 때문에 앱이 매우 반응성이 좋습니다.
4. 미디어 처리의 중요성을 과소평가하지 말 것
이미지/비디오 업로드는 간단해 보이지만 다음을 고려하면 복잡합니다:
- 더 빠른 업로드를 위한 압축
- 썸네일 생성
- 진행 상황 추적
- 업로드 취소
- 용량 제한 적용
- 서명된 URL 관리
우리의 postService.ts가 56 KB인 이유가 있습니다.
5. React Query는 게임 체인저
React Query를 사용하기 전에는 모든 곳에서 수동으로 캐시 무효화를 해야 했습니다. 이제는:
const { data, isLoading, refetch } = useQuery({
queryKey: ['posts', userId],
queryFn: () => fetchUserPosts(userId),
staleTime: 5 * 60 * 1000,
});
// Mutations automatically invalidate related queries
useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
}
});
뮤테이션은 관련 쿼리를 자동으로 무효화합니다.
다음은 무엇인가요?
- 구형 기기에서 성능 프로파일링
- 접근성 감사
- 최종 베타 테스트 라운드
- 앱 스토어 자산 및 스크린샷
따라가고 싶으신가요?
우리는 공개적으로 개발하고 있습니다! 우리의 여정을 팔로우하세요:
- X:
- LinkedIn:
- Landing page & waitlist:
비슷한 것을 만들고 있거나 우리 접근 방식에 대해 질문이 있으면 아래에 댓글을 남겨 주세요. 스택의 어떤 부분이든 더 자세히 공유해 드리겠습니다!
모바일 앱을 만들면서 겪은 기술적 도전 과제는 무엇인가요? 댓글로 이야기해요!


