본 글은 개인적인 삽질의 기록입니다.
틀린 부분 지적이나 다른 방법이 있으면 적극 환영입니다🤗
유저가 질문을 한 뒤 타로 카드를 뽑으면 이를 GPT가 해석해주는 서비스입니다.
(영어 답변이 더 매끄러워요. 한글은 DeepL로 번역을 한 번 더 거쳐요.)
클립보드 복사-붙여넣기 만들다가 승질승질이 나서 SNS 공유하기를 알아봤더니 카카오톡은 더 승질날 수 있다는 후기를 들었다.
그리고 그렇게 예정된 삽질을 하기 시작했다. 분명히 api 문서를 보고 했을 뿐인데…왜 안 되는 거니…온갖 블로그는 "이렇게 하면 됩니다~"하는데 나만 안 돼.
하지만 의외로 트위터는 쉬웠는데.
encodeURI(window.location.href)로 인식 불가능 문자를 UTF-8로 인코딩. 그리고 그걸 넣어준다.
// TODO: 트위터 공유하기
const twitterShare = () => {
const url = `공유할 url`;
const text = `GPT가 알려주는 오늘의 운세`;
const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(
text
)}&url=${encodeURIComponent(url)}`;
window.open(twitterUrl);
};
twitter 아이콘은 X가 아닌 파랑새 아이콘으로…ㅎ fontawesome에 있었다.
카카오톡 공유하기는 별도 API가 필요하다. 카카오 developers에서 모든 걸 확인할 수 있다.
과정은 다음과 같다.
정말 쉽다. 근데 이걸로 이틀을 머리 싸매고 고민했는데 이틀이면 오래 걸린 것도 아니라고 하더라.
내 애플리케이션 메뉴로 가서 등록해준다.
앱 아이콘, 앱 이름은 카카오톡 공유 시에 노출되는 사진 및 타이틀이다. 원래는 GPTarot라고만 하려다가 발음문제로 그냥 한글까지 추가했고, 아이콘은 없어서 GPT와 씨름 끝에 괜찮은 이미지 하나 가져다 박았다.
애플리케이션 등록하고 나면 아래와 같이 화면이 보인다.
해당하는 애플리케이션을 클릭하면 아래와 같이 왼쪽 사이드 메뉴에 플랫폼을 클릭. 자신에게 해당하는 사이트 도메인을 등록하면 된다.
웹 플랫폼 등록을 하는데 아래에 처음에 아래 이미지처럼 가장 마지막 슬래쉬 삭제를 안 했더니 오류가 났다. 가장 마지막 슬래쉬는 넣지 마시길.
문서에서 하라는 대로 따라해보자.
일단 kakao SDK 스크립트를 추가한다.
주의🚨
'카카오톡 공유하기 만들기' 검색해서 나오는 블로그에 있는 코드 긁지말고 여기서 다운로드하자. 왜냐하면 버전이 다를 가능성이 많고 일부 블로그도 기존 블로그에 있는 SDK 다운로드 코드 긁어서 사용한 흔적이 보였다.

이걸 본인의 프로젝트 루트 html에 포함한다. 나는 리액트 프로젝트였기 때문에 src폴더 index.html의 head에 넣어주었다.
그리고 SDK 다운로드를 하고 스크롤을 내리면 초기화와 관련된 설명이 있다. 이 부분으로 인해서 고민하게 되는데 아래에 자세히 설명하도록 하겠다.

내 애플리케이션에서 해당하는 애플리케이션 클릭하면 바로 키가 4개 등장하는데 그 중에 자바스크립트 키를 복사한다.
세번째에 있는 친구이다. 이를 환경변수로 지정해준다.
나는 vercel에 이미 배포를 한 상태였기 때문에 vercel에서 추가 해주었다.
그리고 여기서 주의할 점은 본인의 프로젝트가 create-react-app 등으로 생성한 리액트 프로젝트라면 REACT_APP_으로, vite로 생성한 프로젝트라면 VITE_등 개별 빌드 툴들이 환경변수를 관리하는 방법으로 저장해준다.
리액트에서 환경변수는 process.env.{variable-name}으로 사용하는데 반해, vite에서는 import.meta.env.{variable-name}으로 사용한다
이제 사용할 친구를 고르면 된다.
공유하기 버튼도 카카오톡에서 제공하는 것을 사용할 것인지, 커스텀 할 것인지, 공유하는 내용이 커머스인지, 지도인지, 단순 텍스트인지 등등에 따라서 매우 다양했다.

일단 시작은 가장 단순하게 하는 걸로.
데모 사이트에서 아래 옵션들을 선택했을 때 나오는 예시 코드를 복사했다. 이미지 아래 코드는 SDK 다운로드 코드와 초기화 코드, script 태그를 지운 상태이다.

function shareMessage() {
Kakao.Share.sendDefault({
objectType: 'feed',
content: {
title: '딸기 치즈 케익',
description: '#케익 #딸기 #삼평동 #카페 #분위기 #소개팅',
imageUrl:
'http://k.kakaocdn.net/dn/Q2iNx/btqgeRgV54P/VLdBs9cvyn8BJXB3o7N8UK/kakaolink40_original.png',
link: {
// [내 애플리케이션] > [플랫폼] 에서 등록한 사이트 도메인과 일치해야 함
mobileWebUrl: 'https://developers.kakao.com',
webUrl: 'https://developers.kakao.com',
},
},
social: {
likeCount: 286,
commentCount: 45,
sharedCount: 845,
},
buttons: [
{
title: '웹으로 보기',
link: {
mobileWebUrl: 'https://developers.kakao.com',
webUrl: 'https://developers.kakao.com',
},
},
{
title: '앱으로 보기',
link: {
mobileWebUrl: 'https://developers.kakao.com',
webUrl: 'https://developers.kakao.com',
},
},
],
});
}
위의 예시 코드를 내 서비스에 맞게 커스텀해서 사용했다.
아래가 내 서비스에서 사용할 코드.
function shareMessage() {
Kakao.Share.sendDefault({
objectType: 'feed',
content: {
title: '앱 이름',
description: '공유한 유저의 질문',
imageUrl:
'유저가 뽑은 타로카드 이미지',
link: {
mobileWebUrl: '내 사이트 도메인',
webUrl: '내 사이트 도메인',
},
},
buttons: [
{
title: '결과페이지 이동',
link: {
mobileWebUrl: '결과 페이지 url',
webUrl: '결과 페이지 url',
},
},
{
title: '메인페이지 이동',
link: {
mobileWebUrl: '내 사이트 도메인',
webUrl: '내 사이트 도메인',
},
},
],
});
}
앞서 3번, SDK 다운로드 부분에서 초기화 관련 설정을 하지 않았다.
공식 문서에는 아래와 같이 나와있다. index.html에 스크립트 형태로 초기화 코드가 들어가는 형태이다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Kakao JavaScript SDK</title>
<script src="https://t1.kakaocdn.net/kakao_js_sdk/${VERSION}/kakao.min.js"
integrity="${INTEGRITY_VALUE}" crossorigin="anonymous"></script>
<script>
Kakao.init('API_KEY');
console.log(Kakao.isInitialized()); // true/false 리턴
</script>
</head>
<body></body>
</html>
이 부분이 이제 이틀이 걸린 부분이었는데…
정말 말그대로 Kakao.init()을 어찌 처리해야 하나? 하는 문제였다. index.html에 직접 넣어야 하나? 그럼 내 키가 노출되는데? 그럼 컴포넌트에 넣어? 어떻게 넣어? 그게 맞아?
둘 다 해본 결론부터 말하자면, 두 가지 방식으로 진행이 가능은 하지만 index.html에 직접 코드를 넣는 방식은 사용할 수 없다.
useEffect()를 활용해서 컴포넌트가 렌더링 될 때 초기화하고, if문으로 true/false여부를 확인해주면 된다.
useEffect(() => {
Kakao.init(import.meta.env.VITE_KAKAO_API_KEY);
}, []);
const kakaoShare = async () => {
if(!Kakao.isInitialized()){
Kakao.init(import.meta.env.VITE_KAKAO_API_KEY);
}
try {
// 기타 네트워크 요청 코드 생략
const url = '결과페이지url';
Kakao.Share.sendDefault({
objectType: "feed",
content: {
title: "GPTarot | 지피타로 | 오늘의 운세",
description: '유저가 한 질문',
imageUrl: '이미지url',
link: {
mobileWebUrl: url,
webUrl: url,
},
},
buttons: [
{
title: "결과 보기",
link: {
mobileWebUrl: url,
webUrl: url,
},
},
{
title: "나도 질문하기",
link: {
mobileWebUrl: "https://gptarot.jiwoo.best",
webUrl: "https://gptarot.jiwoo.best",
},
},
],
});
} catch (error) {
console.error(error);
}
};
이렇게 하면 초기화가 컴포넌트 렌더링 시에 최초 발생하고, 이후 초기화 여부를 확인하여 false인 경우 다시 초기화 함수를 호출하는 방식으로 코드가 작동한다.
그럼, 왜 index.html은 안 되는지 확인해보자.
index.html의 코드는 위의 예시와 동일하다.
카카오톡 공유하기가 있는 컴포넌트로 가서 true인지, false인지 체크해주고 초기화가 되지 않았다면 init()하는 함수를 한 번 호출해주도록 설정하면 된다. 따라서, if문으로 한 번 확인해주면 초기화되지 않았다거나, 초기화 코드가 중복되는 경우를 방지할 수 있다.
// 카카오톡 공유하기
const kakaoShare = async () => {
if(!Kakao.isInitialized()){
Kakao.init(import.meta.env.VITE_KAKAO_API_KEY);
}
try {
// 기타 네트워크 요청 코드 생략
const url = '결과페이지url';
Kakao.Share.sendDefault({
objectType: "feed",
content: {
title: "GPTarot | 지피타로 | 오늘의 운세",
description: '유저가 한 질문',
imageUrl: '이미지url',
link: {
mobileWebUrl: url,
webUrl: url,
},
},
buttons: [
{
title: "결과 보기",
link: {
mobileWebUrl: url,
webUrl: url,
},
},
{
title: "나도 질문하기",
link: {
mobileWebUrl: "https://gptarot.jiwoo.best",
webUrl: "https://gptarot.jiwoo.best",
},
},
],
});
} catch (error) {
console.error(error);
}
};
만약 index.html에 자바스크립트 키를 하드코딩 하지 않고 환경변수를 호출하는 방식으로 설정했다면, 당연히 콘솔에 Syntax Error가 뜰 것이다.
그러나 공유하기는 가능하다. 왜냐면 if문에서 초기화가 안됐음을 확인하고 초기화를 여기서 해줬을 거거든.
일단, import.meta는 ES 모듈의 기능이며 index.html 파일에 직접 포함된 것과 같은 기존 스크립트에서는 사용할 수 없다고 한다. vite 프로젝트에서는 일반적으로 자바스크립트 모듈 내에서 환경 변수에 액세스하기 위해 import.meta.env를 사용하지만, 이 엑세스 방식은 html 파일과 같은 글로벌 컨텍스트에서는 작동하지 않는다.
vercel 배포 환경에서 import.meta.env를 활용하지 않고 환경변수를 사용할 수는 있다. 빌드 시에 vercel의 경우 %VITE_ENV_VARIABLE% 이런 코드가 있으면 환경변수가 있다고 인지하는데 이렇게 될 경우 index.html 소스 코드에 환경 변수가 노출되기 때문에 실질적으로 사용할 수는 없다.
나는 현재 타입스크립트를 사용하고 있기 때문에 TypeScript가 인식하지 못하는 전역 변수에 액세스하려고 할 때 흔히 만나는 문제다.
이 경우 타입을 알려주면 된다. window에 kakao가 존재한다고 알리면 되는데 현재 카카오톡 공유하기 기능을 구현 중인 파일에 알려주면 된다. custom 타입 선언 파일(예: declarations.d.ts)에 추가할 수도 있다고 하는데 나는 그냥 작업 중인 파일에 선언했다.
이것저것 import해주는 작업 파일 상단에 아래와 같이 작성한 뒤에 실행하면 아주 잘 된다. 굳굳
declare global {
interface Window {
Kakao: any;
}
}
const { Kakao } = window;
막상 글을 정리하고 보니, 왜 다른 사람들이 쉬웠다고 했는지 알겠다.
너무 단계가 명확하네. 구체적인 방법에서 조금 애먹었을뿐.
근데 다 그렇지 않나🤔
이상 삽질의 기록이었다.
후.