Roomify: React와 서버리스 Puter로 AI 인테리어 비주얼라이저 구축하기
Roomify는 2D 평면도를 몇 초 만에 사실적인 3D 렌더링으로 변환합니다. 이 앱은 프론트엔드에 React 19와 TypeScript를 사용하고, 서버리스 로직, 저장소, AI 생성에는 Puter 플랫폼을 활용합니다. 별도의 백엔드 없이 프론트엔드는 puter.js SDK를 통해 Puter API와 직접 상호작용합니다.
이 아키텍처는 기존 서버를 제거합니다: 파일 업로드, 프로젝트 생성, AI 호출은 모두 워커에서 처리됩니다. 이는 중급 및 시니어 개발자에게 배포와 확장을 단순화합니다.
기술 스택과 역할
- React 19 + TypeScript: Vite를 통한 HMR이 지원되는 타입 안전 프론트엔드.
- React Router 7: 홈페이지와 비주얼라이저를 위한 라우팅.
- Tailwind CSS: 커스텀 CSS 없이 유틸리티 우선 스타일링.
- Lucide React: UI용 아이콘.
- Puter.js: 브라우저에서 파일 저장소, KV, 워커, AI 모델에 접근.
Puter는 서버리스 워커, KV 저장소, 영구 파일 저장소를 제공합니다. 워커는 puter.action으로 호출하고, AI는 puter.ai.generate로 호출합니다.
아키텍처: 업로드부터 렌더링까지
처리 워크플로우:
- 평면도를
puter.file.write에 업로드. - 파일의 공개 URL 획득.
project.create워커 호출하여 메타데이터를 KV에 기록.ai.render3d워커를 통한 생성: 이미지 로드, 프롬프트 작성, Claude/Gemini 호출, 결과 저장.- 렌더링 URL로 KV 업데이트 후 프론트엔드 반환.
프론트엔드는 원본 평면도와 렌더링을 react-compare-slider로 비교하여 표시합니다.
워커 함수: 코드와 로직
JavaScript 워커 함수는 Puter CLI를 통해 업로드됩니다.
프로젝트 생성:
// lib/puter.action.ts
export async function createProject({ item, visibility }) {
const result = await puter.action('project.create', {
item,
visibility,
});
return result;
}
export async function getProjects() {
return await puter.action('project.list');
}
export async function getProjectById({ id }) {
return await puter.action('project.get', { id });
}
렌더링 생성:
// worker: ai.render3d
export default async ({ sourceImage, style = 'photorealistic' }) => {
const imageUrl = typeof sourceImage === 'string' ? sourceImage : await puter.file.url(sourceImage);
const prompt = `
이 평면도를 ${style} 3D 인테리어 렌더링으로 변환하세요.
이미지는 벽, 창문, 문이 있는 방 배치를 보여줍니다.
공간을 가장 잘 보여주는 시점에서 사실적인 뷰를 생성하세요.
적절한 가구, 텍스처, 조명, 그림자를 추가하세요.
전문적인 건축 시각화처럼 보이게 만드세요.
`;
const result = await puter.ai.generate({
model: 'claude-3-sonnet',
prompt,
images: [imageUrl],
responseFormat: 'image'
});
const renderedPath = `/projects/${projectId}/rendered.png`;
await puter.file.write(renderedPath, result.image);
const key = `project:${projectId}`;
const project = JSON.parse(await puter.kv.get(key));
project.renderedImage = puter.file.url(renderedPath);
await puter.kv.set(key, JSON.stringify(project));
return {
renderedImage: project.renderedImage,
renderedPath
};
};
KV는 메타데이터를 저장합니다: 프로젝트 이름, 소유자, 공개 설정, 파일 링크.
프론트엔드: 컴포넌트와 훅
홈페이지(Home)는 프로젝트 목록을 표시합니다. Upload 컴포넌트는 파일을 base64로 변환합니다:
// components/Upload.tsx
const handleFileChange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const base64 = await toBase64(file);
onComplete(base64);
};
비주얼라이저(VisualizerId)는 자동 생성을 위해 useEffect를 사용합니다:
useEffect(() => {
if (isProjectLoading || hasInitialGenerated.current || !project?.sourceImage) return;
if (project.renderedImage) {
setCurrentImage(project.renderedImage);
hasInitialGenerated.current = true;
return;
}
hasInitialGenerated.current = true;
void runGeneration(project);
}, [project, isProjectLoading]);
useRef는 중복 호출을 방지합니다. 상태는 Redux 없이 로컬로 관리됩니다.
일반적인 문제 해결
- CORS: 개발 환경에서 Vite 프록시 사용.
- Base64 크기: 파일을 별도로 저장하고 KV에 링크 저장.
- 이중 생성:
useRef+ 확인 절차. - 비동기성: 로딩 상태, 스켈레톤 UI.
- 모바일 UI: Tailwind 반응형 디자인, 슬라이더 터치 지원.
검증: 최대 10 MB 파일, JPG/PNG 형식. 10-15초 생성 시간에 대한 로더.
핵심 요약
- Puter가 백엔드를 대체합니다: 워커, KV, 파일, AI를 하나의 SDK로.
- React 훅으로 외부 라이브러리 없이 상태 관리.
- 프롬프트가 품질을 정의합니다: 벽, 가구, 조명을 세부적으로 기술.
- 이 아키텍처는 DevOps 없이 프로덕션 규모로 확장 가능.
- 코드는 오픈소스로, 포크와 실험에 적합.
— Editorial Team
아직 댓글이 없습니다.