yceffort

Typescript, React, NextJs, Koa, Styled Component 로 프론트엔드 환경 만들기

Published on June 20, 2019

사용한 오픈소스

React

자세한 설명은 생략 한다

Nextjs

NextJs 리액트에서 서버사이드 렌더링을 할 수 있도록 해주는 프레임워크다. angular나 react 등은 SPA라서 불편한 점이 더러 있는데, React에서 NextJS를 활용하면 react를 ssr(server side rendering)이 되도록 바꿔줄 수 있다. 그리고 자동으로 code splitting이 되고, 파일 시스템을 기준으로 라우팅이 되며, .. 뭐 이런저런 장점이 있다.

koa

express를 만든 개발자들이 따로 떨어져 나와서 만든 web framework가 바로 koa다. express와 비교했을 때는 koa가 비교적 가볍고, node.js v7의 async/await 를 자유자재로 쓸 수 있다는 데 있다. 그리고 es6를 도입해서 generator도 사용할 수 있다. IBM이 express를 인수해버린 관계로, 많은 개발자들이? koa로 넘어가는 추세라고 하는데, 아직은 잘 모르겠다.

Styled Component

Styled Component

시작

package.json

{
  "name": "hello-world",
  "version": "0.0.1",
  "description": "hello-world",
  "main": "main.js",
  "scripts": {
    "build": "tsc --outDir dist server/index.ts && next build",
    "start": "NODE_ENV=production node dist",
    "dev": "concurrently 'tsc -w --outDir dist server/index.ts' 'npm run watch-server -- --delay 2'",
    "watch-server": "nodemon --exec 'node dist' --watch dist -e '*'"
  },
  "author": "",
  "license": "UNLICENSED",
  "dependencies": {
    "@zeit/next-typescript": "^1.1.1",
    "@zeit/next-css": "^1.0.1",
    "@zeit/next-stylus": "^1.0.1",
    "formik": "^1.5.7",
    "isomorphic-fetch": "^2.2.1",
    "koa": "^2.7.0",
    "koa-body": "^4.1.0",
    "koa-bodyparser": "^4.2.1",
    "koa-morgan": "^1.0.1",
    "koa-mount": "^4.0.0",
    "koa-proxies": "^0.8.1",
    "koa-router": "^7.4.0",
    "next": "^8.1.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "styled-components": "^3.4.10"
  },
  "devDependencies": {
    "@types/isomorphic-fetch": "0.0.35",
    "@types/koa": "^2.0.48",
    "@types/koa-bodyparser": "^4.3.0",
    "@types/koa-morgan": "^1.0.4",
    "@types/koa-mount": "^3.0.1",
    "@types/koa-router": "^7.0.40",
    "@types/next": "^8.0.5",
    "@types/node": "^12.0.4",
    "@types/react": "^16.8.22",
    "babel-eslint": "^10.0.1",
    "babel-plugin-styled-components": "^1.10.0",
    "concurrently": "^4.1.0",
    "nodemon": "^1.19.1",
    "npm": "^6.9.0",
    "typescript": "^3.5.1"
  }
}

./typings/koa-proxies/index.d.ts

애석하게도 koa-proxies의 typing이 존재하지 않는다. ./typings/koa-proxies에 아래와 같이 추가하자.

declare module "koa-proxies" {
  import { Middleware } from "koa";
  namespace koaProxies {}
  function koaProxies(name: string, options?: any): Middleware;
  export = koaProxies;
}

타입스크립트로 nextjs를 사용하기 위하여 @zeit/next-typescript를 사용하였다.

./next.config.js

별도의 설정은 넣지 않았다.

const withCSS = require("@zeit/next-css");
const withStylus = require("@zeit/next-stylus");
const withTypescript = require("@zeit/next-typescript");

module.exports = withTypescript(
  withStylus(
    withCSS({
      webpack: config => ({
        ...config,
        plugins: [...(config.plugins || [])],
        node: {
          fs: "empty"
        }
      })
    })
  )
);

./.babelrc

{
  "presets": ["next/babel", "@zeit/next-typescript/babel"]
}

./pages/index.tsx

nextjs의 유일한 제약은 pages 폴더다. pages에 렌더링 할 페이지를 만들어 둬야 한다.

import * as React from "react";
import styled from "styled-components";

const MainHeading = styled.div`
  font-size: 50px;
  color: red;
`;

export default class IndexPage extends React.PureComponent {
  render() {
    return <MainHeading>hello?</MainHeading>;
  }
}

./server/index.ts

가장 중요한 서버 부분이다. koa를 사용한 이유는 */api/*로 요청이 오는 호출에 대해서는 외부에 있을지도 모르는 api서버를 활용하기 위함이다. 이를 별도로 처리 하지 않는다면 CORS이슈가 있을수 있기 때문이다. 그래서 koa를 통해서 nextjs를 호출하는 방식으로 바꾸었다.

import * as next from "next";
import * as Koa from "koa";
import * as morgan from "koa-morgan";
import * as Router from "koa-router";
import * as proxy from "koa-proxies";
import * as bodyparser from "koa-bodyparser";
import * as mount from "koa-mount";

const isDev = process.env.NODE_ENV !== "production";

function renderNext(nextApp: next.Server, route: string) {
  return (ctx: Koa.Context) => {
    ctx.res.statusCode = 200;
    ctx.respond = false;

    nextApp.render(ctx.req, ctx.res, route, {
      ...((ctx.request && ctx.request.body) || {}),
      ...ctx.params,
      ...ctx.query
    });
  };
}

async function main() {
  const nextApp = next({ isDev });
  const app = new Koa();
  const router = new Router();

  await nextApp.prepare();
  const handle = nextApp.getRequestHandler();

  router.get("/", renderNext(nextApp, "/index"));

  app
    .use(morgan("combined"))
    .use(bodyparser())
    .use(
      proxy("/api", {
        target: "https://jayg-api-request.test.com",
        rewrite: (path: string) => path.replace(/^\/api/, ""),
        changeOrigin: true
      })
    )
    .use(
      mount("/health", (ctx: Koa.Context) => {
        handle(ctx.req, ctx.res);
        ctx.status = 200;
      })
    )
    .use(router.routes())
    .use(
      mount("/", (ctx: Koa.Context) => {
        handle(ctx.req, ctx.res);
        ctx.respond = false;
      })
    )
    .listen(3000);
}

main();