Next JS의 Custom Server

Urther·2022년 7월 30일
9

nextJs

목록 보기
5/6
post-thumbnail

이전에 작성했던
[NextJS,Typescript] 카카오 API와 Firebase 연동하기 정리
에서 이어지는 내용입니다. 기술 스택은 NextJS , Typescript 입니다.

Custom Server 도입

왜 Custom Server를 도입하게 되었는가

REF | 카카오의 CORS 정책
즉, 프론트엔드 스크립트에서 kapi.kakao.comAPI호출은 허용되지 않습니다.
CORS가 열려있는 kauth.kakao.comAPI의 경우에도 리다이렉트 URI로 되돌아가야하는 로그인(인가요청, 추가 항목 동의받기), 로그아웃은 ajax 방식으로 호출 할 수 없습니다. 즉, kauth.kakao.comAPI는 토큰 요청만 가능합니다.

카카오 access token을 받아오고, 그것을 이용해서 user Info 를 가져오는 kapi의 CORS에러 때문에 서버가 필요하다고 생각했다.

원래는 Express 서버를 따로 구축했었다. 그런데 면접을 보다가 면접관님께서 내가 구현한 방법을 보시게 되었고, Custom server에 대해 알려주셨다 🥲 그래서 찾아보게 되었다. Next JS에서 Custom Server 가 명시되어 있었는데 왜 몰랐을까. 당연하게 Express 서버를 따로 구축하려고 했다.

서론이 길어졌다. 각설하고, 나는 왜 Next.js 의 Custom Server를 사용하려했는지 요약해보고 싶다. 프로젝트 내에서 no-server의 firebase를 사용한다. 그렇기 때문에 Express 서버가 따로 필요 없다. 그럼에도, 카카오 API 연동 부분에서 단 한부분만이 Express 서버를 필요로 했다. 그래서 Next js 내에서 백엔드 서버를 돌리려한다.

기본 세팅

Next JS typescript 버전 설치

npx create-next-app@latest --ts

# or 

yarn create next-app --typescript

Express와 Nodemon 설치

  • 서버의 코드를 변경할 때마다 재구동하는 일은 귀찮은 일입니다. save 되는 순간 업데이트 될 수 있도록 nodemon을 설치해주었습니다.
npm i express
npm i --save-dev nodemon 

디렉토리 구조

디렉토라 구조는 더 세분화 될 수 있겠지만, 나는 jwt 토큰 발급 및 verify 기능과 카카오 API 기능만 수행해줄 것이기 때문에 일단 server.ts 혹은 server.js로 명명해주었다.

Javascript 를 이용한 Express 서버 구축

server/server.js

import express from "express";
import { createServer } from "http";
import next from "next";

const dev = process.env.NODE_ENV === "development";
const port = 3000;
const app = next({ dev, port });

const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.get("/getRequest", (req, res) => {
    console.log("hello");
  });

  server.all("*", (req, res) => {
    return handle(req, res);
  });

  server.listen(port, (err) => {
    console.log("ready");
  });
});

프론트쪽의 Port 번호와 동일하지 않아도 된다.

일단, getRequest 란 임의적 API를 만들어두었다.

package.json

나는 module 방식을 선호하기 때문에 type 영역에 "module"을 추가해주고, script 부분을 통해 서버를 열어주었다.

{
  ...
  "type": "module",
  "scripts": {
    "dev": "nodemon server/server.js",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  ...
}

next-config.js

next-config.js 안에 CJS 방식대로 선언되어 있기 때문에 파일 명을 next-config.cjs로 변경해주었다.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
}

module.exports = nextConfig

요청해보기

프론트쪽 아무 파일이나 이동해서 실제로 요청이 들어오는지 확인해주었다.

  const checkExpress = async () => {
    try {
      const res = await axios.get("/getRequest");
    } catch (e) {
      console.log(e);
    }
  };

  useEffect(() => {
    checkExpress();
  });

getRequest에 둔 console.log("hello"); 가 출력된 모양이다. (아, next 내에서 build 되어야지 백엔드 쪽이 읽는다.)

Typescript를 이용한 Express 서버 구축

추가 설치

npm i --save-dev ts-node @types/express

위에서 nodemon 은 서버의 재시작이 귀찮아서 썼다면 ts-node는 Node.js 상에서 타입스크립트의 컴파일러를 통과하지 않고도 직접 타입스크립트를 실행하는 역할을 한다. 그리고 @types/express 를 설치해준다.

server/server.ts

import express, { Request, Response } from "express";
import { createServer } from "http";
import next from "next";

const dev = process.env.NODE_ENV === "development";
const port = 3000;
const app = next({ dev, port });

const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.get("/getRequest", (req:Request, res:Response) => {
    console.log("hello");
  });

  server.all("*", (req:Request, res:Response) => {
    return handle(req, res);
  });

  server.listen(port, (err?:any) => {
    console.log("ready");
  });
});

tsconfig.server.json

server 디렉토리 외에 tsconfig.server.json 파일을 만들어준다.

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "noEmit": false
  },
  "include": ["server"]
}
  • extends : tsconfig.json 파일에 있는 모든 옵션들을 가져온다.
  • compilerOptions
    • module :
    • noEmit :
  • include : ['server']

package.json

  "scripts": {
    "dev": "nodemon --watch '*.tsx' --exec 'ts-node' --project tsconfig.server.json server/server.ts ",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },

코드 전체적으로 뜯어보기

✓ next의 dev?


const app = next({dev, port}) 가 보일 것이다. env 파일에 NODE_ENV가 development와 동일할 경우(true)와 동일하지 않을 경우 (false) 를 나누어 두었지만 그냥 bool 값으로 넣어주어도 된다

  • true : 개발 모드
  • false: 배포 모드

개발 모드일 때는 node server.ts 혹은 server.js 를 해주면 되고, 배포 모드일 때는 build 를 진행해준 다음 서버를 실행해준다.

✓ Typescript 버전 서버의 tsconfig.server.json

기존 tsconfig.json 파일은 extends를 통해 다른 파일의 속성을 설정할 수 있다. 따라서 tsconfig.server.json 파일을 만들어서 server와 관련된 ts 규칙을 넣어준 것이다.

  • compilerOptions : tsconfig 컴파일러 옵션 에서 더 다양한 컴파일러 옵션을 확인할 수 있다.

    • module(string): [tsconfig의 모든 것] module 에서 자세한 내용을 확인할 수 있다.

      모듈 코드 생성 지정: "None", "CommonJS", "AMD", "System", "UMD", "ES6", "ES2015" 또는 "ESNext".

    • noEmit(bool) : false 인 경우 출력을 내보내지 않겠다는 의미이다.

모듈을 왜 common JS 방식으로 사용해야 할까?

  • ES6(ESM(ES Module) 방식으로 모듈을 컴파일)도 사용해보았을 때 아래와 같은 SyntaxError가 발생했다.

    Express와 Next 를 함께 쓰려면 CommonJS 방식으로 사용해야 한다는데 왜 그런지 이해를 못하겠다 🥲

✓ body-parser

백엔드 Route 처리에서 request의 Body 부분을 가져올 일이 있다.

예를 들어서, 프론트에서 아래와 같은 요청을 할 경우다.

서버에서는 access token 을 받아오기 위해 또 아래와 같은 작업을 따로 해주어야하는데 이 때 문제가 발생한다.

access_token이 없다는 문구가 떠서 console 을 찍어봐도 undefined 가 뜬다. body-parser을 쓰기 전에는 undefined가 디폴트 값이기 때문이다. 따라서 body-parser을 설치해준다. ⏤ [1분 패키지 소개] : body-parser를 소개합니다. 하지만, body-parser를 쓰지 마세요.

npm i body-parser

js, ts 상관 없이 위와 같이 작성하면 잘 작동할 것이다 !

npm i cookie-parser
npm i --save-dev @types/cookie-parser

백엔드에서 jwt 토큰을 처리하는 경우에는 res.cookie로 받아야할 경우가 있다. 그 때 cookie-parser가 없다면 빈 객체로 넘어오거나 undefined가 뜰 수 있다. 그래서 설치해서 express와 연결해주었다.


Reference |
티스토리 블로그 - express로 Next 커스텀 프론트 서버 구축하기
Set Up Next.js with a Custom Express Server + Typescript
Next.js 튜토리얼 6편: 서버 사이드

profile
이전해요 ☘️ https://mei-zy.tistory.com

0개의 댓글