๐ ๋ค์ด๊ฐ๋ฉฐ
์ด ํฌ์คํ ์ Next.js 11 ๋ฒ์ (page router) ํ๊ฒฝ์์ ์์ฑ๋ ๊ธ์ ๋๋ค.
13๋ฒ์ ์ดํ app router ํ๊ฒฝ์์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ด ํฌ์คํ ์์ 1 ~ 1-3๊น์ง๋ง ๋ณด์๊ณ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
Next.js + Typescript ํ๊ฒฝ์์ ํ ์ด ํ๋ก์ ํธ๋ฅผ ์งํํ๋ ์ค ์นด์นด์คํก์ผ๋ก ๊ณต์ ํ๊ธฐ ๊ธฐ๋ฅ์ด ํ์ํด์ ๊ตฌํ์ ํ์๋๋ฐ, ๋ช๋ช ์ค๋ฅ๋ ๋ง์ฃผ์น๊ณ ๋ฐ๋ก ์ ๋ฆฌ๋ฅผ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ์์ ์ด๋ ๊ฒ ์ ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
1. ์ ํ๋ฆฌ์ผ์ด์ ๋ฑ๋กํ๊ธฐ
์ผ๋จ ์ฒซ ๋ฒ์งธ๋ก ๊ฐ๋ฐํ๊ธฐ์ ์์์ Kakao Developers์์ ์ดํ๋ฆฌ์ผ์ด์ ๋ฑ๋ก์ ํด์ค์ผ ํฉ๋๋ค.
1-1 ๋ก๊ทธ์ธ
์ฌ์ดํธ์ ์ ์ ํด์ฃผ์๊ณ ์ฒซ ํ๋ฉด์์ ๋ก๊ทธ์ธ์ ํด์ฃผ์๊ณ '๋ด ์ ํ๋ฆฌ์ผ์ด์ '์ ํด๋ฆญํด์ฃผ์ธ์.
1-2 ์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐํ๊ธฐ
๊ทธ๋ฌ๋ฉด ์ด๋ ๊ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ถ๊ฐํ ์ ์๋ ํ๋ฉด์ด ๋์ต๋๋ค.
์ฌ๊ธฐ์ '์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐํ๊ธฐ'๋ฅผ ๋๋ฌ์ฃผ์ธ์.
'์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐํ๊ธฐ'๋ฅผ ํด๋ฆญํ๋ฉด ์ด๋ ๊ฒ ์ถ๊ฐํ ์ ์๋ ๋ชจ๋ฌ ์ฐฝ์ด ๋์ต๋๋ค.
์ฑ ์ด๋ฆ์๋ ๋ง๋ค๊ณ ์ ํ๋ ์๋น์ค ์ด๋ฆ์ ๊ธฐ์ฌํด์ฃผ์๊ณ ,
์ฌ์ ์๋ช ์๋ ์ฌ์ ์๋ช ์ด ์์ผ๋ฉด ๋ณธ์ธ ์ด๋ฆ์ ์ ์ด์ฃผ์ ๋ ๊ด์ฐฎ์ต๋๋ค.
์ถ๊ฐ๊ฐ ์๋ฃ ๋์์ผ๋ฉด ํด๋น ์ถ๊ฐํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฝ์ ๋ณด์๋ ๋ณธ์ธ์ด ์์ฑํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ ํค๋ค์ด ์กด์ฌํฉ๋๋ค.
์ฌ๊ธฐ์ ์ ํฌ๋ JavaScript ํค๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋๋ค.
1-3 Web ํ๋ซํผ ๋ฑ๋ก
์ข์ธก ๋ฉ๋ด์์ ํ๋ซํผ์ด๋ผ๋ ๋ฉ๋ด๋ฅผ ํด๋ฆญํด์ฃผ์๊ณ ์ ํฌ๊ฐ ์ฌ์ฉํ ์ฝํ ์ธ ๋ Web์ด๋๊น 'Web ํ๋ซํผ' ๋ฑ๋ก ๋ฒํผ์ ํด๋ฆญํด์ฃผ์๋ฉด ์๋์ ๊ฐ์ ํ๋ฉด์ด ๋์ค๋๋ฐ, ์ฌ๊ธฐ์ ์ ํฌ๊ฐ ๊ฐ๋ฐํ๊ฒฝ์์ ์ฌ์ฉํ ์๋ฒ ์ฃผ์์, ๋ฐฐํฌ๋ฅผ ํ์ จ์ผ๋ฉด ๋ฐฐํฌํ ์ฃผ์๋ ๊ฐ์ด ์ ์ด์ฃผ์๋ฉด ๋ฉ๋๋ค.
์ด์ ์นด์นด์คํก ๊ณต์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ค๋น๋จ๊ณ๋ ๋๋ฌ์ต๋๋ค.
2. ์ ์ฉ
2-1 JavaScript SDK ์ถ๊ฐํ๊ธฐ
์ด์ ํด๋น ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ SDK๋ฅผ ์ถ๊ฐํด ์ค์ผ ํฉ๋๋ค.
<script defer src="https://developers.kakao.com/sdk/js/kakao.min.js"></script>
์์ ๊ฐ์ script ํ๊ทธ๋ฅผ <head> ์ฌ์ด์ ์์ฑํด ์ค์ผ ํ๋๋ฐ,
next.js์์๋ _documnet์์ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉํ <head> ๋๋ <body> ํ๊ทธ ๋ฑ ์์ ๋ค์ด๊ฐ ๋ด์ฉ๋ค์ ์ปค์คํ ํ ๋ ์ฌ์ฉํฉ๋๋ค. ์ด์ ๋ํ ์์ธํ ๋ด์ฉ์ ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
https://nextjs.org/docs/advanced-features/custom-document
๊ทธ๋ผ ์นด์นด์คํก ๊ณต์ ๊ธฐ๋ฅ์ ๊ตฌํํ ํ๋ก์ ํธ์ ๊ตฌ์ฑ์ ํด์ฃผ๋๋ก ํฉ์๋ค.
'./pages/_document.tsx'
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<script
defer
src="https://developers.kakao.com/sdk/js/kakao.min.js"
></script>
<Head/>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
2-2 env
์ฒ์์ Kakao Developers์์ ์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐํ๊ณ ์์ฝ ํญ์์ ๋ดค๋ JavaScript ํค๋ฅผ envํ์ผ๋ก ์์ฑํด์ค์๋ค.
API Key๋ ์ฝ๋์ ์ง์ ๋ช ์ํ๋ ๊ฒ๋ณด๋ค ๋ณด์์ envํ์ผ๋ก ์์ฑํด ๋๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ฃจํธ ํด๋์ .env ํ์ผ์ ์์ฑํ๊ณ ์๋์ ๊ฐ์ด ์์ฑํด์ฃผ์ธ์.
(API ํค๋ฅผ ์์ฑํ ๋ ' ' ๋ฐ์ดํ๋ ํ์ ์์ต๋๋ค.)
'.env'
NEXT_PUBLIC_KAKAO_API_KEY=APIํค
2-3 initialize
์ด์ JavaScript ํค๋ฅผ ์ด์ฉํด์ initialize๋ฅผ ํด์ค์ผ ํฉ๋๋ค.
initialize๋ ์๋์ ๊ฐ์ด ํด์ค ์ ์์ต๋๋ค.
์์ ์ปดํฌ๋ํธ (ex. Layout or _app)์์ ๋ง์ดํธ ๋ ๋ ์ ์ฉ๋ ์ ์๋๋ก ํด์ค์๋ค.
ํ๋์ ํ์ด์ง์์๋ง ๊ณต์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ๊ฒ์ด๋ผ๋ฉด ์ฌ์ฉํ ํ์ด์ง์์ ์์ฑํด๋ ๋ฉ๋๋ค.
์ ๋ ์ฌ๋ฌ ํ์ด์ง์์ ์ฌ์ฉ์ ํ๊ธฐ ์ํด์ ์์ ์ปดํฌ๋ํธ(Layout.tsx)์ ์์ฑํด์ฃผ์์ต๋๋ค.
ex)
'./src/components/common/Layout.tsx'
import GlobalStyle from 'styles/GlobalStyle';
import styled from 'styled-components';
import { size } from 'styles/theme';
import Footer from './Footer';
import DarkModeBtn from '../Buttons/DarkModeBtn';
import { useEffect } from 'react';
const Wrapper = styled.div`
width: 100%;
max-width: ${size.mobile};
min-height: 100vh;
margin: auto;
padding: ${({ theme }) => theme.padding.base};
display: flex;
flex-direction: column;
justify-content: center;
`;
interface IProps {
children: React.ReactNode;
}
const Layout = ({ children }: IProps) => {
useEffect(() => {
window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_API_KEY);
}, []);
return (
<Wrapper>
<GlobalStyle />
<DarkModeBtn />
{children}
<Footer />
</Wrapper>
);
};
export default Layout;
๊ทธ๋ฆฌ๊ณ ์ด๋ ๊ฒ๋ง ์์ฑํ๊ฒ ๋๋ฉด window ๊ฐ์ฒด ์์ Kakao๊ฐ ์๋ค๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒ์ ๋๋ค.
window ๊ฐ์ฒด์ Kakao๊ฐ ์๋ค๊ณ ์๋ ค์ค์ผ ํฉ๋๋ค.
์ ์ญ์ผ๋ก window interface๋ฅผ ์ ์ํด์ค์๋ค.
'./pages/_app.tsx'
import type { AppProps } from 'next/app';
import Layout from 'src/components/common/Layout';
import ThemeProvider from 'src/context/ThemeProvider';
declare global {
interface Window {
Kakao: any;
}
}
function MyApp({ Component, pageProps }: AppProps) {
return (
<ThemeProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
);
}
export default MyApp;
TypeScript์์ any ํ์ ์ ์ต๋ํ ์งํฅํ๋ ๊ฒ์ด ์ข์ง๋ง, Kakao์ type์ ๋ฐ๋ก ์ ์ ์์ด์ any๋ก ๋ช ์ํ์ต๋๋ค.
์ด๋ ๊ฒ ๋ช ์๋ฅผ ํด์ฃผ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๊ณ window ๊ฐ์ฒด์ Kakao์ ์ ๊ทผ์ด ๊ฐ๋ฅํฉ๋๋ค.
2-4 onClick function
์ ๋ ์นด์นด์ค ๊ณต์ ๊ธฐ๋ฅ์ ํ๋ ๋ฒํผ ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ์์ฑํ์ต๋๋ค.
import Button from '../common/Button';
import styled from 'styled-components';
import KakaoIcon from 'public/images/kakaotalk_logo_icon_147272.svg';
const SButton = styled(Button)`
display: flex;
justify-content: center;
align-items: center;
`;
const KakaoBtn = () => {
const onClick = () => {
const { Kakao, location } = window;
Kakao.Link.sendScrap({
requestUrl: location.href,
});
};
return (
<SButton fontColor="yellow" borderColor="whiteYellow" onClick={onClick}>
<KakaoIcon viewBox="0 0 60 48" width="30px" height="23px" />
์นด์นด์คํก ๊ณต์ ํ๊ธฐ
</SButton>
);
};
export default KakaoBtn;
๋ฒํผ์ ํด๋ฆญํ์ ๋ ์คํํ๋ onClick ํจ์์์ ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋๋ฅผ ์์ฑํด ์ฃผ์์ต๋๋ค.
์๋์ ๊ฐ์ ๋ถ๋ถ์ requestUrl์ด ๊ณต์ ํ Url์ ๋๋ค.
location.href๋ฅผ ์์ฑํด ์ค์ผ๋ก์จ ํ์ฌ ์์นํด์๋ URL์ ๊ณต์ ํ ์ ์๋๋ก ํ์ต๋๋ค.
Kakao.Link.sendScrap({
requestUrl: location.href,
});
๊ทธ๋ฆฌ๊ณ ์ค์ํ ๋ถ๋ถ์ ์นด์นด์ค ๊ณต์ ๋ฐฉ์์ ์์ด์ ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
ex)
https://developers.kakao.com/docs/latest/ko/message/js-link
์ ๋ SEO๋ฅผ ์ํด ํ์ด์ง์ meta og tag๋ฅผ ์์ฑํด์ ๊ณต์๋ฌธ์(์์ ๋งํฌ)๋ฅผ ์ฐธ๊ณ ํด์ ์คํฌ๋ฉํ ์น ํ์ด์ง ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ๋ฉ์์ง ๋ด์ฉ์ ๊ตฌ์ฑํ ์ ์๋๋ก Kakao.Link.sendScrap ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
์ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ๋ณด์๋ฉด ์ง์ ๋ฉ์ธ์ง ์ ๋ชฉ, ๋ด์ฉ ๋ฑ ๊ตฌ์ฑํ ์ ์๋ ๋ฉ์๋๋ ์์ต๋๋ค.
์ ๋ ๋ง์ ๊ฒ๋ค ์ค sendScrap๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ฝ ๊ฐ๋ฐ ํ๊ฒฝ(localhost) ํ๊ฒฝ์์ sendScrap๋ฅผ ํตํ ๊ณต์ ๊ธฐ๋ฅ์ ํ ์คํธํ ์ ์นด์นด์ค์์ locahost:3000 ์ฃผ์๋ฅผ ์คํฌ๋ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ์ ๋ชฉ, ๋ด์ฉ ๋ฑ ์ ๋๋ก ํ์๊ฐ ๋์ง ์๋ ๊ฒ ์ ์์ ๋๋ค.
๋ณ๋ค๋ฅธ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๋๋ค๋ฉด ํ๋ก์ ํธ๋ฅผ ๋ฐฐํฌํ ์ฃผ์์์ ํ ์คํธ๋ฅผ ํด๋ณด์๋ฉด ์ ์์ ์ผ๋ก ๋ฉ์์ง ๋ด์ฉ์ด ๋ํ๋๋ ๊ฑธ ํ์ธํ ์ ์์ต๋๋ค.
๐ ๋ง๋ฌด๋ฆฌ
Next.js + TypeScript ํ๊ฒฝ์์ ์นด์นด์คํก์ผ๋ก ๊ณต์ ํ๊ธฐ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ดค์ต๋๋ค.
ํน์ ์ดํด๊ฐ ์ ๋์๊ฑฐ๋ ๊ตฌํํ๋ ๋ฐ์ ์์ด์ ๋ชจ๋ฅด์๋ ๋ถ๋ถ์ด ์์ผ์๋ฉด ๋๊ธ ๋จ๊ฒจ์ฃผ์ธ์!