Next.js와 Typescript를 연습하는 목적으로 진행한 음악 플레이어, 검색 프로젝트 입니다.
이름은 플루티아(Floutia)이다.
기술스택은 새롭게 Next로 서버사이드렌더링을 해보고 TypeScript로 타입 지정을 연습하고 에러를 줄이는 용도로 써보고 싶어 선택했다.
React + TypeScript 기반으로 Next까지 같이 학습하기 좋은것 같다.
외부 API는 Spotify로 선택했다.
선정한 이유는 음악의 데이터가 다양하고 글로벌해서 다양한 데이터를 가져올수 있기 때문에 선택했다.
음악을 플레이 하려면 로그인이 필요한데, 로그인을 하기 위해서는 토큰 처리 과정을 거쳐야한다.
우선 로그인을 하기 위해서 토큰을 발급받아야 한다.
크게 두가지 토큰으로 나뉜다.
Spotify 개발자 공식문서
Request an access token 부분을 보시면 자세히 설명이 되어있다.
로그인이 유지 되어야지만 스포티파이 서비스를 이용할수 있기 때문에 따로 access token이 만료되었을때 재발급하는 코드를 작성해야 한다.
import axios from 'axios';
import { getCookie, setCookie } from 'cookies-next';
import { postRefreshToken, postClientCredentialsToken } from 'api/token';
import { BASE_API_URL } from 'constants/path';
const api = axios.create({
baseURL: BASE_API_URL,
});
api.interceptors.request.use(
async (req) => {
if (typeof window !== 'undefined') {
try {
const access_token = getCookie('access_token');
const refresh_token = getCookie('refresh_token');
if (typeof access_token === 'string' && access_token !== '') {
req.headers['Authorization'] = `Bearer ${access_token}`;
return req;
}
if (typeof refresh_token === 'string' && refresh_token !== '') {
const { data } = await postRefreshToken(refresh_token);
req.headers['Authorization'] = `Bearer ${data.access_token}`;
setCookie('access_token', data.access_token, {
maxAge: data.expires_in,
});
return req;
}
const { data } = await postClientCredentialsToken();
req.headers['Authorization'] = `Bearer ${data.access_token}`;
setCookie('access_token', data.access_token, {
maxAge: data.expires_in,
});
} catch (error) {
return req;
}
}
return req;
},
(error) => Promise.reject(error)
);
api.interceptors.response.use(
(res) => res,
async (error) => {
const { config, response } = error;
if (response?.status === 502 && !config._retry) {
config._retry = true;
return api(config);
}
if (response?.status === 401 && !config._retry) {
try {
config._retry = true;
const refresh_token = getCookie('refresh_token');
if (typeof refresh_token === 'string' && refresh_token !== '') {
const { data } = await postRefreshToken(refresh_token);
api.defaults.headers.common['Authorization'] = `Bearer ${data.access_token}`;
setCookie('access_token', data.access_token, {
maxAge: data.expires_in,
});
return api(config);
}
const { data } = await postClientCredentialsToken();
api.defaults.headers.common['Authorization'] = `Bearer ${data.access_token}`;
setCookie('access_token', data.access_token, {
maxAge: data.expires_in,
});
return api(config);
} catch (error) {
return Promise.reject(error);
}
}
return Promise.reject(error);
}
);
export default api;
Access Token은 스포티파이 디벨롭퍼 사이트에 들어가셔서 개발자 모드에서 Client_id와 Client_secret을 넣으면 발급이 된다.
curl -X POST "https://accounts.spotify.com/api/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=your-client-id&client_secret=your-client-secret"
실제로 한줄한줄 입력해 보시면 발급이 되는지 확인할수 있다.
그리고 access token은 1시간동안만 유지된다.
Refresh Token은 클라이언트 애플리케이션이 사용자가 애플리케이션을 다시 인증할 필요 없이 새 액세스 토큰을 얻을 수 있도록 하는 보안 자격 증명이다.
RefreshToken을 axios로 받는 예제 이다.
const getRefreshToken = async () => {
// refresh token that has been previously stored
const refreshToken = localStorage.getItem('refresh_token');
const url = "https://accounts.spotify.com/api/token";
const payload = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId
}),
}
const body = await fetch(url, payload);
const response await body.json();
localStorage.setItem('access_token', response.accessToken);
localStorage.setItem('refresh_token', response.refreshToken);
}

전체적인 플로우 중 1번과 2번까지 진행한 것이다.
로그인페이지를 리다이렉트하는 페이지도 설명드리면
import type { NextApiRequest, NextApiResponse } from 'next';
import qs from 'querystring';
import { BASE_URL } from 'constants/path';
import generateRandomString from 'utils/generateRandomString';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const state = generateRandomString(16);
const scope =
'user-read-private user-read-email user-read-playback-state user-modify-playback-state streaming';
res.redirect(
'https://accounts.spotify.com/authorize?' +
qs.stringify({
response_type: 'code',
client_id: process.env.NEXT_PUBLIC_CLIENT_ID,
scope: scope,
redirect_uri: `${BASE_URL}/api/callback`,
state: state,
})
);
}
실제 next를 사용하여 querystring을 사용해 값을 받아와주어야 한다.
안그러면 에러가 뜨기 때문에..
랜덤으로 문자열을 생성하는 generateRandonString함수를 만들어 util로 빼놨다.
왜냐하면 서버 컴포넌트라 클라이언트 컴포넌트를 쓰면 에러가 난다.
앱 라우터와 페이지 라우터가 저는 조금 햇갈렸는데 앱라우터는 상위폴더 app을 쓴다.(생략가능)
하지만 페이지 라우터는 pages라는 폴더 안에 실제 라우터 명을 적고 그 안에 index.ts 파일을 생성해주어야한다.

실제로 router를 이동할때는 next의 기능중 router를 사용하여 .push()로 이동하여도 되지만
next의 Link기능을 사용해 < a > 태그와 같이 사용해야 한다.
처음에 Link를 통해서 사용하다가 router도 한번 써보고 싶어서 사용하고 있다.
어떤것을 사용해도 무방하지만 개인적으로 Link가 더 편한것 같다.
간략하게 Next 공식문서에서 라우터에 대해서 배우고 적용해 보았는데 서버사이드렌더링을 통해 웹페이지 구조를 짜는 방법에 대해서 배워봤다.
실제로 요청하는 로직은 다음 포스팅에서 설명드리도록 하겠다.
출처
주기님 블로그