본문으로 건너뛰기
yceffort
PostsSeriesTagsAbout🧪 Research
EN

Tweaks

theme
accent palette
film grain
minimal mode
BACK TO INDEX
◆ ESSAY
--min
--year
KOoriginal

mailMail icongithubtwitter
yceffort
•
© 2026
•
https://yceffort.kr
BACK TO INDEX
◆ ESSAY

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

avatar
yceffort
2019-06-21 · 4분
4min
2019year
KOoriginal
nextjsfrontendtypescript

이 문서는 더 이상 업데이트 하지 않을 생각이다. 대신 https://github.com/yceffort/koa-nextjs-react-typescript-boilerplate 여기에서 계속 해서 만들어 가고 있다.

사용한 오픈소스

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()

관련 글

  • ◆ 블로그 성능 개선하기 · 3편
    #performance#web-vitals#nextjs

    블로그를 고친 뒤 첫 화면을 다시 재봤다

    블로그 마이그레이션 뒤 수식 글 LCP가 5.6초로 늘어난 원인을 추적했다. 일반 웹 글꼴을 실제 코드에서 제거하고 다시 빌드하자 수식 글 LCP는 5,650ms에서 2,352ms, FCP는 1,514ms에서 758ms로 줄었다. LCP 대상은 본문 문단에서 배너로 바뀌었다. 수식 전용 글꼴은 쓰는 글리프만 남긴 서브셋으로 바꿔 334KiB에서 26KiB가 됐다. 최초 비교 48회, 원인 대조 15회, 수정 전후 24회, 서브셋 전후 8회의 결과를 기록했다.

    2026-09-14·44분
  • ◆ 블로그 성능 개선하기 · 2편
    #rust#wasm#markdown

    블로그의 마크다운 파이프라인을 Rust/WASM으로 옮기기

    블로그의 remark/rehype 체인을 Rust로 옮기고 WASM으로 빌드해 Next.js 서버에 붙였다. 파싱과 HAST 생성에 이어 Oniguruma 하이라이트, MathML 수식, 이미지 크기와 MDX 속성 처리까지 한 호출로 묶었다. 메모리 전달과 해제, 기존 글의 호환성, WASI와 바이너리 배포를 구성하며 얻은 것과 감수한 비용을 기록했다.

    2026-09-14·61분
  • ◆ 블로그 성능 개선하기 · 1편
    #stylex#tailwind#css

    Tailwind를 StyleX로 옮기며 다시 따져본 성능

    블로그의 Tailwind 4를 StyleX로 옮겼다. 유틸리티만 옮긴 중간 상태에서는 CSS와 FCP가 줄었지만 전체 전송량은 늘었다. 전환 범위를 넓히자 홈 FCP가 59% 느려졌고, 작성 방식을 고친 뒤에도 격차가 남았다. 마지막 개선은 KaTeX 조건부 로딩과 본문 CSS 분리에서 나왔다. CSS 크기와 전체 전송량, 페인트 지표를 따로 비교한 기록이다. 블로그 성능 개선하기 시리즈의 첫 편이다.

    2026-09-14·51분
  • #typescript#oxc#eslint

    typescript@7을 설치하면 벌어지는 일들: 블로그 모노레포 마이그레이션 기록

    pnpm lint가 12분 32초 걸리던 모노레포에 typescript 7.0.2를 넣어봤다. 타입체크는 조용히 지나갔는데 next build가 깨졌고, lint는 크래시했다. eslint와 prettier를 oxlint와 oxfmt로 갈아탄 하루의 연쇄 반응과 전후 실측 기록. 미리 말해두면, 빌드는 빨라지지 않았다.

    2026-08-10·17분

새 글을 놓치고 싶지 않으시다면 RSS로 구독해 주세요.

RSS 구독 →

yceffort — 프론트엔드 엔지니어입니다.

← Back to the blogIssue on GitHub →