기프티콘 거래 플랫폼


프리뷰

🐯 프로젝트 소개

기프티콘 거래 플랫폼의 웹페이지를 클로닝하는 프로젝트를 진행했다.
nextjstypescript를 사용하였으며, SSR에서의 데이터 및 props 전달, 그리고 E2E 테스트에 대해서도 학습할 수 있는 좋은 프로젝트 경험이었다.

SSR 및 Nextjs 관련하여 학습 후 정리한 블로그 포스트


🤴 배포 주소

https://gifticon-market.vercel.app/


🗂 CNA 구조

CNA 구조


🔡 텍스트 데이터의 정제

이번 프로젝트에서 가장 애를 먹은 파트였다.
받아온 API 데이터와 실제 웹페이지의 텍스트 구조가 달랐기 때문에, 최대한 똑같이 만들어보고 싶은 욕심에 텍스트를 직접 정규표현식 및 유틸함수를 활용하여 정제 후 활용해봤다.

받아온 데이터

API 데이터는 위처럼 받아와졌지만, 실제 화면에는 아래와 같이 입력되어야 했다.

보여져야 하는 형식

따라서 getDescription()이라는 유틸함수를 직접 만들어서 API 데이터를 넣어서 보내주면, 실제 뷰와 동일한 형식의 텍스트 템플릿으로 받은 후 활용할 수 있도록 기능을 구현했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// utils/getDescription.ts

import { Description } from '@types';

export const getDescription = async (desc: string) => {
// 1. 텍스트를 descriptions에 담아서 return해줄 것이다.
let descriptions: Description[] = [];
// 2. 위의 사진에서 볼 수 있듯이, [사용 불가 매장]이라는 라벨은 쓰이지 않는다. 따라서 아래와 같이 replace를 통해 삭제했다.
const sliceRestricted = desc && desc.replace('\n[사용 불가 매장]\n', '');
// 3. 그리고 각 주제별 텍스트들을 분리하기 위해 아래와 같이 본문들을 분리했다. (두 번의 띄어쓰기가 쓰인다는 공통점을 이용)
const splitDesc = sliceRestricted.split('\n\n');
// 4. [OOO] 또는 *OOO 형태로 쓰여진 라벨을 본문과 분리하기 위해 아래와 같이 정규표현식을 사용했다.
const normalRegex = new RegExp('\\[.*\\n', 'g');
const voucherRegex = new RegExp('\\*.*\\n', 'g');
// 이하 생략 -> 아래에서 설명 예정
return descriptions;
};

위의 선언 코드들을 통해 데이터를 정제하기 위한 준비를 끝냈다. 이제 해당 데이터들을 어떻게 분리하여 템플릿에 맞게 정제했는지 아래의 코드를 통해 설명해보도록 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 위의 코드 const voucherRegex 바로 아래에 붙는 코드다. (각 배열 요소들에 대한 명령을 주기 위해 forEach 활용)
splitDesc.forEach((text, i) => {
// 만약 배열 요소에 [OOO]형식의 라벨이 있다면 rawLabel에 [OOO]형식을, 아니면 *OOO형식을 담는다.
const rawLabel = text.match(normalRegex)
? text.match(normalRegex)![0]
: text.match(voucherRegex)![0];
// 그리고 이제 양 옆에 있는 '[', ']'를 템플릿과 동일하게 삭제해준다.
const labelString = rawLabel.replace('[', '').replace(']', '');
// 그리고 라벨을 제외한 본문 내용을 descText에 담아주고, 템플릿과 동일하게 -를 ·으로 바꿔준다.
const descText = text.slice(rawLabel.length).replaceAll('-', '·');
// 이제 라벨과 본문으로 나눠진 텍스트를 하나의 객체에 담아 descriptions에 push해준다.
const newDesc = { label: labelString, text: descText };
descriptions.push(newDesc);
});
// 함수를 호출한 곳에 descriptions를 리턴해준다
return descriptions

이렇게 하면 내가 원하는 템플릿에 맞게 rawTextData를 정제하여 뷰에 띄워줄 수 있다.
조금 하드코딩식으로 코드를 짜긴 했지만, 문제 없이 작동됐기에 추후 리팩토링이 가능하다면 공간복잡도 개선을 위해 forEach 내부에서 const들을 선언해주는 것이 아닌, 바깥에서 선언과 할당이 이루어질 수 있는 방법도 고려해보고자 한다.


🧦 meta태그

SEO를 위해 pages 디렉토리 안에 _document.tsx 파일을 만든 후 meta태그를 작성했다.

기존 CRA에서 작업을 할 때에는, 그냥 public의 index.html 파일에 들어가 meta, font, title, body 등에 대한 코드들을 직접 작성해줄 수 있었다. 하지만 NextJS의 경우에는 이를 직접 index.html 파일을 찾아 수정하는 것이 아닌, pages 디렉토리에 _document.tsx 파일을 생성 후 head와 body태그를 작성해줘야 한다.

아래의 코드는 본 프로젝트에서 사용한 _document.tsx 파일이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<meta
name='description'
content='모바일 쿠폰 중고거래 사이트. 남들은 정가로 살 때, 나는 할인가로 산다'
/>
<meta
name='keywords'
content='쿠폰,상품권,기프티콘,기프티콘판매,기프티콘구매,기프티콘추천,기프티콘앱,편의점기프티콘,기프티콘선물,기프티콘사용법,모바일쿠폰,모바일상품권,중고쿠폰,중고장터,e쿠폰,할인쿠폰'
/>
<meta
name='og:title'
content='니콘내콘 - 국내 1위 기프티콘 플리마켓'
/>
<meta
name='og:description'
content='모바일 쿠폰 중고거래 사이트. 남들은 정가로 살 때, 나는 할인가로 산다'
/>
<link rel='icon' href='/favicon.ico' />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

export default MyDocument;
  • 코드 최상단에 next/document 에서 import한 Html, Head, Main, NextScript 태그(컴포넌트)는 빌드후에 html, head, main, script 태그가 된다.
  • 이 컴포넌트들을 위에서 import하지 않으면 Next.js가 제대로 서버 사이드 렌더링을 할 수가 없다.
  • 또한, _document.js는 서버에서만 실행되는 파일이므로 window.alert와 같은 전역객체 메소드를 사용할 수 없다.

meta 태그?

메타태그는 HTML문서에 대한 주요 정보(요약본)를 담고 있는 태그다.

meta태그는 (x)html 문서가 어떤내용을 담고있는지, 그리고 핵심키워드는 무엇이며, 저자가 누구인지 어떤 언어로 작성되었는지 등의 정보를 담고있다. 이 메타태그는 브라우저 뷰에는 표시되지 않지만 검색엔진이 쉽게 크롤링을 할 때 읽을 수 있다.

위에서 사용한 <meta name=og:OOO>는 카카오톡이나 노션 등에 링크를 붙여넣기 했을 때 나타나는 정보다. 아래 사진을 보면 쉽게 이해할 수 있을 것이다.

og:title, og:description, og:image 등등


🧪 Cypress를 활용한 E2E테스트

처음으로 E2E 테스트라는 것을 해봤다.
E2E테스트란 End to End 테스트로, 개발한 웹사이트를 유저의 입장에서서 직접 테스트 해보는 테스트를 뜻한다.

내가 만든 캐러셀이 잘 슬라이딩 되는지, 라우팅은 잘 되는지, 데이터가 잘 입력되는지 등등 여러 구현 테스트를 진행할 수 있다.

본 프로젝트에서는 이러한 E2E 테스트를 위해 cypress프레임워크를 선정했다. 가장 다운로드 수도 많았으며, 래퍼런스를 찾기 용이할 것 같다는 판단 하에서였다.

Cypress 설치

1
2
3
$ npm install cypress --save-dev

$ yarn add cypress -D

기본 설정 (package.json, tsconfig.json)

아래와 같이 두 파일들을 수정해주면 된다.

1
2
3
4
5
6
7
8
9
// package.json

"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"cypress:open": "cypress open"
},
1
2
3
4
5
6
// tsconfig.json

"compilerOptions": {
...
"types": ["cypress"]
},

Cypress 실행해보기

1
2
3
$ npm run cypress:open

$ yarn cypress:open

cypress

그러면 이와 같은 cypress 브라우저가 실행될 것이다. (필자는 사전에 test파일들을 모두 삭제했기 때문에 기본설치 파일들이 나타나지 않는것이니 본인 파일들과 다르다고 해서 걱정하지 않아도 된다.)

test코드 작성하기

cypress/integration 디렉토리에 first.spec.tsx와 같이 임의의 테스트 파일을 생성해주자

그리고 아래와 같이 기본적인 테스트 코드를 작성해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <reference types="cypress"/>

context('Home Page', () => {
// 모든 테스트 실행 전에 아래 주소를 visit 한다.
beforeEach(() => {
cy.visit('http://localhost:3000');
});

it('h1태그를 찾아 니콘내콘이라는 텍스트를 포함하는지 테스트', () => {
cy.get('h1').contains('니콘내콘');
});
});

export {};

분명 오류가 날텐데, 아래의 설치과정만 마무리 하면 된다

1
2
3
$ npm install start-server-and-test --save-dev

$ yarn add start-server-and-test -D

그리고 package.json파일에 한 줄의 코드를 더 작성해준다.

1
2
3
4
5
6
7
8
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"cypress:open": "cypress open",
"test": "start-server-and-test dev 3000 cypress:open"
},

이제 yarn test 또는 npm run test를 통해 작성한 테스트 케이스를 실행해볼 수 있다.


포스팅에 참고한 자료

How to create integration test with Cypress in your Next.js project


Author

Hoonjoo

Posted on

2022-02-17

Updated on

2022-02-17

Licensed under

Comments