카카오톡 Share, 이메일 EmailJS 공유 SDK + SEO 개발일지
기존 서비스 개선 방향
기존의 공유 형식은 브라우저 기본 url을 사용해 사용자가 직접 공유하거나, url copied 버튼을 통해 복사 기능을 제공하였습니다. 이에 따라 사용자 경험이 부족하다고 느끼게 되었습니다.
다양한 공유 방법을 직접적으로 제공해주면, 함께 친구들과 공유하여 사용하는 서비스적 측면이 향상될 것이라고 생각하였습니다.
카카오톡 소셜 로그인, 구글 소셜 로그인을 제공하는 싱크스팟 서비스 이기 때문에, 활용하여서 카카오톡, 이메일 공유하기 기능을 고려하게 되었습니다.
추가로 많이 사용하는 인스타그램 서비스를 활용한 공유하기 기능도 고민하였으나, Meta에서 공식적으로 지원하는 적절한 API 가 없음에 따라 안타깝게도 제외하였습니다.
💌 카카오톡 공유하기 Share
카카오톡 소셜 로그인, 카카오맵 사용을 위해 생성해둔 kakao developers 의 syncspot 애플리케이션에 Share 기능을 추가하였습니다.
*아래 글은 developers 애플리케이션 등록 이후 과정을 기록하였습니다.
🚨 SEO 태그
기존에 우리 링크를 카카오톡에 공유하면 아래처럼 표시됩니다.
아무런 정보가 없는 상태에서 -> 썸네일 이미지, 제목, 내용 등을 표시하도록 설정하겠습니다.
OG (Open Graph) 오픈그래프 태그
오픈 그래프는 페이스북이 기존의 메타데이터 표기 방법을 참조하여 만들었습니다.
HTML 파일의 메타정보를 쉽게 표시하기 위해서 제목, 설명, 이미지 등의 요소들을 사용할 수 있도록 정의해놓은 프로토콜 입니다.
페이스북 뿐만 아니라, 트위터, 링크드인, 카카오, 네이버 등에서 사용되고 있기 때문에 등록했습니다.
☑️ 기본 태그
- og:title - 사이트의 제목
- og:type - 사이트의 종류 예) website
- og:image - 사이트를 나타낼 대표 이미지(미리보기 이미지)
- og:url - 사이트의 대표 url
📍 프로젝트 코드
SEO 정보, OG 태그 메타정보, twitter cards 도 함께 등록해 주었습니다.
추가로, 구글 애널리틱스로 실사용자 및 서비스 운영관리를 하고 있기 때문에 관련 스크립트도 추가했습니다.
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
/>
<title>Syncspot - 모두가 편하게 만나는 지름길</title>
<!-- Primary Meta Tags -->
<meta name="title" content="Syncspot - 모두가 편하게 만나는 지름길" />
<meta
name="description"
content="중간지점 찾기부터 시간/장소 투표까지 한 번에 해결하세요."
/>
<meta
name="keywords"
content="Syncspot, 중간지점 찾기, 약속잡기, 장소 투표하기, 시간 투표하기, 모임, 만나기, 위치 공유"
/>
<meta name="author" content="모락 Morak" />
<meta name="robots" content="index, follow" />
<meta name="language" content="Korean" />
<meta name="revisit-after" content="7 days" />
<meta name="generator" content="Syncspot" />
<meta name="copyright" content="© 2024 Morak" />
<meta name="coverage" content="Worldwide" />
<meta name="distribution" content="Global" />
<meta name="rating" content="General" />
<meta name="geo.region" content="KR" />
<meta name="geo.placename" content="Seoul" />
<link rel="canonical" href="https://syncspot.co.kr/" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://syncspot.co.kr/" />
<meta
property="og:title"
content="Syncspot - 모두가 편하게 만나는 지름길"
/>
<meta
property="og:description"
content="중간지점 찾기부터 시간/장소 투표까지 한 번에 해결하세요!"
/>
<meta
property="og:image"
content="https://private-user-images.githubusercontent.com/96279437/..."
/>
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://syncspot.co.kr/" />
<meta
property="twitter:title"
content="Syncspot - 모두가 편하게 만나는 지름길"
/>
<meta
property="twitter:description"
content="중간지점 찾기부터 시간/장소 투표까지 한 번에 해결하세요!"
/>
<meta
property="twitter:image"
content="https://private-user-images.githubusercontent.com/96279437/..."
/>
</head>
<!-- Google tag -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=%VITE_GOOGLE_ANALYTICS_ID%"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', '%VITE_GOOGLE_ANALYTICS_ID%');
</script>
<!------------------------>
- meta 정보를 업데이트 했는데도 이미지 등 정보가 변경이 안되었다면, 카카오 서버에 페이지의 정보가 아직 캐싱되지 않은 것입니다. (빠르면 1시간 이내 최대 24시간 이내에 적용 시간이 걸린다고 합니다.)
- 캐싱 삭제를 진행하고 싶으면, 카카오 개발자 사이트에서 가능합니다. (페이스북 캐싱 삭제)
⚙️ 카카오톡의 공유하기 api 사용하기 위한 설치 방법
이 링크에서 SDK 를 다운로드 해주어야 합니다. /download 페이지로 들어가서 Full SDK </> 를 클릭해서 버전에 따른 코드 링크를 복사합니다.
프로젝트 폴더 최상단의 index.html 파일 body 태그 안에 붙여넣습니다. integrity 와 corssorigin 은 애플리케이션, 버전마다 다르게 지정됩니다.
또한, Kakao 스크립트가 재랜더링 시에 키를 init 해주어야 합니다. 초기화 코드의 키는 환경변수로 지정한 카카오 애플리케이션 키를 사용합니다.
<script src="https://t1.kakaocdn.net/kakao_js_sdk/2.7.4/kakao.min.js"
integrity="sha384-DKYJ~~~~"
crossorigin="anonymous"
></script>
<script>
Kakao.init('%VITE_KAKAO_JAVASCRIPT_KEY%');
</script>
공유 이벤트 리스너
공식 홈페이지의 문서 > 메시지 > 카카오톡 공유: JavaScript 에서 사용방법을 확인합니다.
위 문서를 보면 공유 API, 메시지 API 두 종류가 존재하는 것으로 판단됩니다. 싱크스팟 프로젝트는 메신저 내용을 전달하는 것이 아닌, 싱크스팟의 기능을 공유하여 함께 서비스를 사용하는 것이 목표이기 때문에 공유 API 를 사용하였습니다.
앞서 세팅에서 설치한 SDK 의 Kakao.Share 모듈을 사용하면 공유 기능을 구현할 수 있다고 설명합니다.
syncspot 프로젝트는 타입스크립트를 사용하고 있기 때문에 Kakao 형식을 declare global 로 지정을 해야 합니다.
기존에는 카카오맵의 kakao 모듈 을 사용하고 있습니다. 대소문자를 구분하여 kakao 모듈과 Kakao 모듈은 다르기 때문에 두가지 모두 선언해주어야 사용할 수 있습니다. - 타입스크립트
declare global {
interface Window {
kakao: any;
Kakao: any;
}
}
카카오톡에서 제공하는 템플릿 중 피드 feed 를 선택하였습니다.
이외에도 리스트, 현재 위치 공유, 커머스, 텍스트, 캘린더 가 있습니다.
직접 만든 버튼을 사용하기 위해 useKakaoShare 커스텀 훅에서 Kakao.Share.sendCustom() 함수를 사용하였습니다.
import { ShareType } from '@src/types/shareType';
interface IShareKakao {
descriptionType: ShareType;
url: string;
}
export function useShareKakao({ descriptionType, url }: IShareKakao) {
if (window.Kakao) {
window.Kakao.Share.sendDefault({
objectType: 'feed',
content: {
title: 'SyncSpot 중간지점찾기',
description: descriptionType,
imageUrl:
'https://private-user-images.githubusercontent.com/96279437/...',
link: {
webUrl: url,
},
},
buttons: [
{
title: '자세히 보러 가기',
link: {
webUrl: url,
},
},
],
});
} else {
console.error('Kakao SDK is not loaded.');
}
}
이때 ShareType 를 상수로 지정하여서 변동에 유연하게 적용하도록 하였습니다.
export const SHARE_TYPE = {
LOCATION_ENTER: '내 장소를 입력하고 중간 지점을 찾아보세요!',
LOCATION_RESULT:
'입력한 장소를 기준으로 중간 지점을 정했어요. 여기서 만날까요?',
LOCATION_RECOMMENDATIONS: '중간 지점 근처에서 가기 좋은 장소를 추천해드려요!',
PLACE_CREATE:
'중간 지점 후보 중에서 고를 수 있도록 장소 투표 방을 만들어보세요.',
PLACE_VOTE:
'모임 장소를 결정해야 한다면? 중간 지점 후보 중에서 원하는 곳에 투표해 주세요.',
PLACE_RESULT: '투표 완료! 이번 모임은 여기에서 만나요.',
TIME_CREATE: '언제 만날지 정할 수 있도록 시간 투표 방을 만들어 보세요.',
TIME_VOTE: '만날 수 있는 날짜와 시간을 투표해 주세요!',
TIME_RESULT: '투표 결과! 이번 만남은 이때 진행돼요.',
ABOUT: '싱크스팟 서비스와 팀 모락에 대해 알아보아요!',
} as const;
export type ShareType = (typeof SHARE_TYPE)[keyof typeof SHARE_TYPE];
이 타입에 해당할 경우에 동작하도록 카카오 버튼 컴포넌트를 구성했습니다.
import IconOauthKakao from '@src/assets/icons/IconOauthKakao.svg?react';
import { useShareKakao } from '@src/hooks/share/useKakaoShare';
import { SHARE_TYPE, ShareType } from '@src/types/shareType';
import { PATH } from '@src/constants/path';
export interface IShare {
url: string;
}
export default function KakaoShare({ url }: IShare) {
const selectedRoomId = localStorage.getItem('selectedRoomId');
if (selectedRoomId === null) {
return false;
}
const pathToShareTypeMap: Record<string, ShareType> = {
[PATH.LOCATION_ENTER(selectedRoomId)]: SHARE_TYPE.LOCATION_ENTER,
[PATH.LOCATION_RESULT(selectedRoomId)]: SHARE_TYPE.LOCATION_RESULT,
[PATH.LOCATION_RECOMMENDATIONS(selectedRoomId)]:
SHARE_TYPE.LOCATION_RECOMMENDATIONS,
[PATH.PLACE_CREATE(selectedRoomId)]: SHARE_TYPE.PLACE_CREATE,
[PATH.PLACE_VOTE(selectedRoomId)]: SHARE_TYPE.PLACE_VOTE,
[PATH.PLACE_RESULT(selectedRoomId)]: SHARE_TYPE.PLACE_RESULT,
[PATH.TIME_CREATE(selectedRoomId)]: SHARE_TYPE.TIME_CREATE,
[PATH.TIME_VOTE(selectedRoomId)]: SHARE_TYPE.TIME_VOTE,
[PATH.TIME_RESULT(selectedRoomId)]: SHARE_TYPE.TIME_RESULT,
[PATH.ABOUT]: SHARE_TYPE.ABOUT,
};
const handleKakaoShare = () => {
const descriptionType = pathToShareTypeMap[window.location.pathname];
if (descriptionType) {
useShareKakao({ descriptionType, url });
} else {
console.error('매칭되는 SHARE_TYPE이 없습니다.');
}
};
return (
<button
className="flex items-center w-full gap-2 p-2 rounded-lg lg:p-4 hover:bg-gray-200"
onClick={handleKakaoShare}
>
<IconOauthKakao className="size-6" />
<span>카카오톡</span>
</button>
);
}
💌 이메일 공유하기 EmailJS
가장 많이 사용되는 Gmail API 를 고려해보았으나, 링크공유 목적과 적합하지 않은 다양한 부가기능이 존재했기 때문에 사용을 피하게 되었습니다.
링크 관련된 정보를 담은 메시지를 입력한 이메일로 전송하는 과정이었기 때문에
- 서버를 거치지 않을 필요가 있는지
- 템플릿 + 링크 변수를 지정할 수 있는지
- 무료 플랜
을 고려하여 API 를 탐색하였습니다.
Send email directly from your code | EmailJS
No server side code required. Add static or dynamic attachments, dynamic parameters, captcha code and more. Start with our free tier!
www.emailjs.com
- 서버 설정 없이 클라이언트 측에서 직접 API 를 통해 이메일을 보낼 수 있음
- 템플릿, 변수 지정 가능. 원하는 메시지 전송도 가능
- 월 200개까지 무료
보다 간단한 과정으로 이메일을 전송할 수 있는 라이브러릴 EmailJS 를 선택하게 되었습니다.
⚙️ EmailJS 서비스 설정
Services 생성 > Gmail > ConnectAccount 이메일 전송할 지메일과 연동합니다.
Service ID 의 경우 env 에서 사용됩니다.
Eamil Templates 에서 메일 템플릿을 커스텀 할 수 있습니다. {{변수명}} 을 지정해서 템플릿을 지정할 수 있어요.
프로젝트 form 태그의 자식요소 name과 같아야 합니다.
message 에 공유할 싱크스팟 링크를 전달하고, 서비스회원의 이름을 지정하였습니다.
* 이미지 첨부는 Code Editor html 수정에서 가능합니다.
공유 이벤트 리스너 SDK
라이브러리 설치
$ npm install --save @emailjs/browser $ yarn add @emailjs/browser
* 카카오와 마찬가지로 useEamilShare.ts 훅을 만들어서 호출해서 사용했습니다.
import { useState } from 'react';
import emailjs from '@emailjs/browser';
import { useGetUserInfoQuery } from '@src/state/queries/users/useGetUserInfoQuery';
interface ISendEmail {
toEmail: string;
message: string;
}
export function useEmailShare() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const SERVICE_ID = import.meta.env.VITE_EMAILJS_SERVICE_ID;
const TEMPLATE_ID = import.meta.env.VITE_EMAILJS_TEMPLATE_ID;
const PUBLIC_KEY = import.meta.env.VITE_EMAILJS_PUBLIC_KEY;
//공유하는 유저 이름 호출
const { data: userInfo } = useGetUserInfoQuery();
const sendEmail = ({ toEmail, message }: ISendEmail) => {
setLoading(true);
const templateParams = {
to_email: toEmail,
from_name: userInfo.data.name,
message: message,
};
emailjs
.send(SERVICE_ID, TEMPLATE_ID, templateParams, PUBLIC_KEY)
.then(() => {
setSuccess(true);
setError(null);
setTimeout(() => setSuccess(false), 5000);
})
.catch(() => {
setError('이메일 전송에 실패했습니다. 다시 시도해주세요.');
setSuccess(false);
setTimeout(() => setError(null), 5000);
})
.finally(() => {
setLoading(false);
});
};
return { sendEmail, loading, error, success };
}
메일 전송 시간이 꽤 소모됨에 따라 loading 상태를 지정하여 … 으로 표시하였습니다.
templateParams 에서 템플릿 변수명과 데이터를 맞추었습니다.
env 값의 경우 아래에서 확인!
- SERVICE_ID > Email Services 메뉴
- TEMPLATE_ID > Email Templates 메뉴
- PUBLIC_KEY > account > public key