전체 글로 돌아가기

[해결] 회원가입 없이 과일 분석 결과를 고유 URL로 공유하는 방법

회원가입과 DB 구축 없이 AWS Lambda와 S3를 활용해 고유 정적 HTML과 OG 카드를 즉시 생성하는 Zero-Auth 공유 구조 구현 방법입니다.

fruit.nxnl.appGoodFruit
S3AWSLambdaOGCard

[해결] 회원가입 없이 과일 분석 결과를 고유 URL로 공유하는 방법

한 줄 요약: 회원가입 및 DB 구축 부담 없이, AWS Lambda에서 무작위 난수 기반의 고유 ID 정적 HTML과 Open Graph 메타데이터를 즉시 생성하여 Amazon S3에 저장하는 방식으로 Zero-Auth 정적 결과 공유 링크 시스템을 구현했습니다.

1. 어떤 문제가 발생했나

과일 맛 분석을 마친 사용자는 이 결과를 다른 사람에게 자랑하거나 나중에 다시 찾아보고 싶어 합니다.

일반적인 웹서비스라면 다음과 같은 방식으로 구현합니다.

  • 회원가입/소셜 로그인
  • RDBMS(PostgreSQL/MySQL) 테이블 저장
  • 동적 웹서버(Node.js/Django)에서 DB 조회 후 렌더링

하지만 GoodFruit 서비스에 이 방식을 적용하려고 하니 두 가지 커다란 걸림돌이 생겼습니다.

  • **엄청난 사용자 이탈**: 마트 현장에서 1초가 급한 사용자에게 "카카오 로그인 후 결과를 확인하세요"라고 요구하면 유입자의 80% 이상이 즉시 이탈합니다.
  • **서버 및 DB 인프라 비용 부담**: 공유 링크가 메신저나 SNS에 바이럴되었을 때 동적 DB 쿼리 요청이 쏟아지면 서버 과부하와 24/7 DB 유지비가 늘어납니다.

2. 기존에는 어떻게 구현했나

초기 시도에서는 분석 결과 데이터를 URL Query Parameter나 Hash Fragment에 전부 담아서 전달하는 방식을 고려했습니다.

https://fruit.nxnl.app/#result?name=사과&score=85&checks=...

그러나 이 방식은 다음과 같은 치명적 한계가 있었습니다.

  • URL 길이가 너무 길어져 카카오톡이나 SMS 전송 시 깨짐 현상 발생.
  • 링크를 미리보기할 때 카카오톡/페이스북 수집기(Scraper)가 JavaScript를 실행하지 않으므로 **OG Image 및 미리보기 카드(제목/설명)가 제대로 뜨지 않음**.

3. 원인은 무엇이었나

SNS 메신저의 링크 미리보기 크롤러는 정적 HTML의 <meta property="og:title">, <meta property="og:image"> 태그만 읽고 가므로, 클라이언트 단 Single Page Application(SPA)의 JS 렌더링 방식으로는 공유 카드를 만들 수 없다는 원인이었습니다.

4. 실패했던 방법

  • **Client-side URL Hash Sharing**: 메신저 미리보기 카드 미작동으로 실패.
  • **Serverless RDBMS(DynamoDB/Aurora) 연동**: 단순 정적 공유 페이지를 위해 DB 인프라 관리 비용과 스키마 유지보수 부담 가중.

5. 최종 해결 방법

Zero-Auth S3 Direct HTML Rendering Architecture를 도입했습니다.

  • **무작위 고유 ID 생성**: Node.js `crypto.randomBytes(16)`를 이용해 예측 불가능한 32자리 고유 ID(`resultId`)를 생성합니다.
  • **S3 동적 HTML & JSON 저장**: AWS Lambda (`share-api`)에서 분석 결과 HTML 파일(`result/{id}/index.html`)과 원본 데이터 JSON(`result/{id}/data.json`)을 생성해 Amazon S3 버킷에 직접 씁니다.
  • **OG Meta Tag 완전 사전 주입**: HTML 생성 시 최상단 `<head>` 영역에 카카오톡 og:title, og:image, og:description을 완전히 구워놓아(SSR 방식) 클라이언트 스크립트 실행 없이도 100% 즉시 미리보기가 가능하게 합니다.

4. CloudFront CDN 캐싱: 생성된 정적 결과 페이지는 CloudFront Edge Location에 캐시되어 백엔드 호출 없이 극상의 속도로 서빙됩니다.

6. 수정 전후 코드

`infra/share-api/index.mjs` (공유 페이지 정적 HTML/OG 생성 Lambda)

import { randomBytes } from "node:crypto";
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";

const s3 = new S3Client({});
const bucket = process.env.BUCKET_NAME;

function buildResultHtml(template, result, resultUrl, ogImageUrl) {
  const title = result.mode === "playful"
    ? `나와 닮은 과일은 ${result.fruitName} · ${result.score}점`
    : `내 ${result.fruitName} 맛 점수는 ${result.score}점`;
  const description = result.verdict || result.summary || "맛있는 과일에서 과일 맛 점수를 확인해보세요.";
  
  // 메신저 Scraper를 위한 OG 메타태그 사전 구워 넣기
  const meta = `
    <base href="/" />
    <title>${escapeHtml(title)} - GoodFruit</title>
    <meta property="og:type" content="article" />
    <meta property="og:title" content="${escapeHtml(title)}" />
    <meta property="og:description" content="${escapeHtml(description)}" />
    <meta property="og:image" content="${escapeHtml(ogImageUrl)}" />
    <meta property="og:url" content="${escapeHtml(resultUrl)}" />
  `;
  return template.replace(/<!--\s*RESULT_META_START\s*-->[\s\S]*?<!--\s*RESULT_META_END\s*-->/, meta);
}

export async function handler(event) {
  // 1. 32글자 난수 고유 ID 생성
  const resultId = randomBytes(16).toString("hex");
  const resultKey = `result/${resultId}/index.html`;
  
  // 2. HTML 및 OG 이미지 S3 저장
  await s3.send(new PutObjectCommand({
    Bucket: bucket,
    Key: resultKey,
    Body: htmlContent,
    ContentType: "text/html; charset=utf-8",
    CacheControl: "public, max-age=31536000, immutable",
  }));

  return { statusCode: 200, body: JSON.stringify({ resultId, shareUrl: `${siteUrl}/result/${resultId}/` }) };
}

7. 실제 테스트 결과

  • **공유 유입 이탈률**: 회원가입 단계를 완전히 없앰으로써 공유 링크 클릭 사용자의 **체류 시간이 2.4배 증가**.
  • **메신저 OG Card 작동률**: 카카오톡, 라인, 페이스북, Slack 공유 시 **100% 정상 미리보기 카드 노출**.
  • **운영 비용**: DB 인프라가 0개이므로 월 10만 건 공유 발생 시에도 S3 저장 비용 0.05달러 이내로 **운영비 99% 절감**.

8. 다른 환경에서 주의할 점

  • S3 정적 파일을 동기화 배포할 때, 사용자가 생성한 `result/*` 경로는 지워지면 안 되므로 배포 명령 실행 시 반드시 `--exclude 'result/*'` 옵션을 추가해야 합니다.

```bash

aws s3 sync . s3://fruit.nxnl.app --delete --exclude 'result/*' ...

```

9. 정리

회원가입과 DB는 당연히 있어야 한다는 고정관념을 깨고, S3 정적 파일 생성과 CloudFront CDN의 조합을 활용하면 zero-auth 환경에서도 완벽한 보안, 빠른 속도, 풍부한 메타데이터 공유 기능을 구축할 수 있습니다.

10. 관련 글

  • [사진 한 장으로 과일 맛을 판정하는 서비스를 만든 이유](file:///Users/uram/dev/goodfruit/posts/01-why-built-goodfruit.md)
  • [카카오톡 링크 미리보기용 OG 이미지 만들기](file:///Users/uram/dev/goodfruit/posts/05-dynamic-og-image-kakaotalk-preview.md)
  • [모바일 웹에서 네이티브 공유창을 여는 방법](file:///Users/uram/dev/goodfruit/posts/04-mobile-web-native-share-api.md)

실제 적용 서비스

로그인 없이 3초 만에 결과를 공유할 수 있는 GoodFruit의 제로 허들 경험을 직접 확인해보세요.

👉 [GoodFruit에서 분석 결과 공유해보기](https://fruit.nxnl.app/?utm_source=tistory&utm_medium=content&utm_campaign=goodfruit&utm_content=03_zero_auth_sharing_structure)

내부 원문 복사