728x90

next.js를 next install 하여

 

out 폴더를 그대로 배포하면 

 

새로고침 후 index페이지로 이동된다거나

 

~~~/register 와 같이 페이지에 접속했을때도 index페이지가 보여지는 등의 이슈가 발생한다

 

이를 해결하기 위해서는

 

node 서버에서 next.js앱을 실행해주어야한다.

 

node server 설정은

 

const { application } = require('express');
const express = require('express');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const handle = nextApp.getRequestHandler();

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

        app.get('*', (req, res) => {
            return handle(req, res);
        });

        app.listen(3000, err => {
            console.log('>>> server is ready on port 3000');
        });
    })
    .catch(err => {
        console.log(err);
        process.exit();
    });

 

이렇게 간단히 할수있고

 

실행은

 

node server.js 이다

 

pm2를통해 무중단 배포를 할때는

 

// package.json

"start": node server.js

 

// 실행

pm2 start npm --name 'next' -- start

728x90
반응형
728x90

취미생활로 브이로그 제작을 시작하였습니다.

 

파일정리가 필요해 지다보니

 

귀찮은 작업을 최소화 하기위해

 

파일 정리 라이브러리를 만들었습니다. (비디오 파일 정리 세팅 되어있음)

 

필요 하신 분들은 이용하시면 될것같습니다.

 

https://github.com/jaekwangLee/organize_video

 

GitHub - jaekwangLee/organize_video

Contribute to jaekwangLee/organize_video development by creating an account on GitHub.

github.com

 

728x90
반응형
728x90

새로운 프로젝트를 시작하며

여러가지 상황을 고려해

next.js로 개발을 진행중입니다.

 

개발에서 가장 어려운건

환경 세팅이라고 그랬던가...

 

역시나 순탄하게 흘러가는 법이 없네요.

 

next.js에서 페이지 핫 로딩 후

styled-component가 먹통이 되는일이 발생하였네요.

다른 분들도 겪을듯 하여 해결 방법을 공유해봅니다.

 

// package.json

"babel": {
    "env": {
      "development": {
        "presets": ["next/babel"],
        "plugins": [
          [
            "styled-components",
            {
              "ssr": true,
              "displayName": true
            }
          ]
        ]
      },
      "production": {
        "presets": ["next/babel"],
        "plugins": [
          [
            "styled-components",
            {
              "ssr": true,
              "displayName": false
            }
          ]
        ]
      }
    }
  }
728x90
반응형
728x90

기획 및 자료조사

오늘 신규 서비스에 대한 아이디어가 나왔고

제품을 만들어 보기로했습니다.

서비스 컨셉과 간략한 기획을 기반으로

필요 기술과 적정 수준 (개발 노고에 대한)에

따라 아키텍쳐를 정의하였습니다.

 

정의

1. 주요 기능 정리

2. 아키텍쳐

3. 라이브러리 (예상)

 

 

위 3가지 큰 틀 안에서 서비스를

명세하였고, 관련해서 충분히 레퍼런스는 있는지

저작권이나 비용적으로 문제는 없는지를

파악하였습니다.

 

Next.js + Node.js + Mongo

결론은 크게 위 3가지로 방향을 잡았습니다.

어디로 튈지 모르는 스타트업 서비스의 특성상

mongo는 정말 찰떡이 아닌가 싶습니다.

 

이전과 다르게 이번 서비스는 SEO와

Scale-Up이 중요하다 판단되어

언제든 새 팀원이 추가되어도 쉽게 적용할수있게

골격이 어느정도 잡혀있다고 하는

Next.js를 채택하기로 하였습니다.

 

API서버로는 Node.js를 채택하였는데,

요구사항을 맞추는데 전혀 문제가 없고

개발 비용면에서도 익숙한 환경이라는 점에서

효율적이기 때문에 선택하게 되었습니다.

특히, Next.js도 처음 도입하는 마당에

서버까지 낯선 환경에 빠지게되면

큰 비용으로 기업이라는 배가 침몰하게되지는

않을까 하는 생각까지 들었습니다. (오바좀 보태서)

 

새로운 프로젝트를 만들어 나가며

어떤 문제 상황들을 맞딱드리는지

위 기술들이 어떤지에대해 주관적인 의견과

역경들을 남겨보도록 하겠습니다.

728x90
반응형

'개발, 코딩' 카테고리의 다른 글

git 협업하기 - 실무편  (0) 2022.10.23
Webpack 구성 이해하기  (0) 2022.10.04
Rest API 정리  (0) 2022.09.29
java zulu jdk11 설치  (0) 2022.07.31
M1 Mac, homebrew install (Warning: /opt/homebrew/bin is not in your PATH.)  (0) 2022.07.31
728x90

< 5탄 - 모바일 결제 플로우에 맞는 코드리뷰 >

 

이전 글들을 못보신 분들을 위해 앞선 글들의 링크를 제공합니다.

< 1탄 - 프롤로그 >

https://blog.self-made.cloud/240

< 2탄 - Code 먼저 공개합니다. >

https://blog.self-made.cloud/241

< 3탄 - 결제 플로우가 어떻게 되는가 >
https://honeystorage.tistory.com/242 

< 4탄 - 웹 결제 플로우에 맞는 코드리뷰 >
https://honeystorage.tistory.com/243

 

 

아임포트 없이 이니시스 결제모듈 연동하기를 진행중입니다.

그 마지막 여정인대요.

 

이전에도 언급했다싶이

이니시스에서는 웹과 모바일 API를 각각 제공합니다.

따라서, 연동도 각각 나눠서 해주어야 합니다.

 

이번에는 마지막으로

모바일 연동에대해 알아보도록 하겠습니다.

 

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 모듈을 실행함
4. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  4-1. 팝업을 닫은 경우, 결제 실패 페이지로 보내거나, 기존의 페이지로 돌려보냄
  4-2. 결제 요청을 완료한 경우, 서버로 요청이 전송됨
5. 서버에서 요청을 처리 및 데이터를 저장하고 결제완료 페이지로 Redirect시킴
6. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌

 

1~3 과정을 통해 결제 모듈일 실행합니다.

// client/src/pages/shop

const onOrderRequest = () => {
  if (isMobile()) return mobileOrder();
  else return webOrder();
};

const newOrder = () => {
  return axios.get('/v1/inicis/new/order');
};

const mobileOrder = async () => {
  const { data: info } = await newOrder();
  const { status, data } = info;

  if (status === 'success') {
    orderNumber = data;

    axios.get('/v1/inicis/m/request/form', { params: { orderId: data } }).then(resolve => {
      const { data: info } = resolve;
      const { status, data } = info;

      if (status === 'success') {
        const form = document.createElement('form');
        form.method = 'post';
        form.acceptCharset = 'UTF-8';
        form.hidden = true;
        form.id = 'pay_form';
        form.action = 'https://mobile.inicis.com/smart/payment/';

        for (let o in data) {
          const input = document.createElement('input');
          input.name = o;
          input.value = data[o];
          input.hidden = true;
          form.appendChild(input);
        }

        document.querySelector('#shop-page').appendChild(form);
        form.submit();
      } else {
        alert('요청 실패');
      }
    });
  }
};

 

서버쪽 코드도 살펴볼까요

const getMobileRequestForm = async (req, res) => {
  const { orderId } = req.query;
  if (!orderId) {
    return res.send({ status: 'error', data: 'check parameters' });
  }

  try {
    // 요청된 orderId가 DB에 실제로 존재하는지 체크
  } catch (error) {
    console.log('check exist order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }

  try {
    const price = 100; // 실제로는 요청된 상품 정보를 조회

    const dataset = {
      P_INI_PAYMENT: 'VBANK',
      P_MID: process.env.MID,
      P_OID: orderId,
      P_AMT: price,
      P_GOODS: 'Sample',
      P_UNAME: '홍길동',
      P_NEXT_URL: getServerDomain() + '/v1/inicis/m/pay/after',
      P_NOTI_URL: getServerDomain() + '/v1/inicis/confirm/payment',
      P_HPP_METHOD: 1,
      P_CHARSET: 'utf8', // <-- 결과값 한글깨짐 방지
    };

    return res.send({ status: 'success', data: dataset });
  } catch (error) {
    console.log('make request form : ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};

 

웹 결제보다는 그 연동과정이 훨씬 간단합니다.

이니시스에서 나중에 만든 API라

신경을 더 많이쓴것 같습니다.

 

모바일 연동이라면 처음 진행해도 어렵지않게

진행할수 있지 않을까 싶네요.

 

나머지 과정은 서버로 들어온 결과를 처리하고 데이터를 저장하거나

알맞는 페이지로 사용자를 Redirect 시켜주는 정도인대요.

 

어렵지 않으니 코드를 보며 진행하면 될것같습니다.

 

여기까지 고생 너무 많으셨고요

성공적인 결제연동 되어서

비용 절약하셨기를 바랍니다.

 

다만, 이니시스 뿐만 아니라

여러 PG사를 연동하고자 한다면

개발 시간 비용을 고려했을때

아임포트 같은 서비스를 쓰는게 합리적인 선택이라고

생각이 드네요 ^^

 

728x90
반응형
728x90

< 4탄 - 웹 결제 플로우에 맞는 코드리뷰 >

 

이전 글들을 못보신 분들을 위해 앞선 글들의 링크를 제공합니다.

< 1탄 - 프롤로그 >

https://blog.self-made.cloud/240

< 2탄 - Code 먼저 공개합니다. >

https://blog.self-made.cloud/241

< 3탄 - 결제 플로우가 어떻게 되는가 >
https://honeystorage.tistory.com/242 

 

이니시스 연동을 위한 웹 결제 플로우가 아래와 같다고 3탄에서 소개했는데요.

제법 복잡하죠.. (더 단순화 할수 있을것같지만 더 만지기가 싫더라구요 ㅠㅠ)

먼저, 웹 버전의 플로우는 이와같습니다.

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 폼을 서버로 요청
4. 서버에서 결제를 진행하기위한 데이터들을 클라이언트로 반환
5. 클라이언트에서 서버로 이니시스 결제모듈 요청 (팝업형태)
6. 서버에서 이니시스 결제모듈 반환
7. 클라이언트에 팝업 모듈이 열림 + 이때부터 팝업 모듈 상태를 주기적으로 체크
8. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  8-1. 닫은경우 팝업 열람전 페이지 유지 혹은 기타 처리
  8-2. 팝업에서 결제요청한 경우 서버로 요청이 전송됨 + 팝업이 닫힘
9. 서버에서 요청 받은 정보들을 통해 결제요청 처리 및 데이터 저장
10. 팝업 모듈이 닫힌게 체크되면 결제요청 완료 페이지로 이동
11. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌

 

 

플로우별로 어떤 코드가 어디에 해당하는지 살펴보도록 하겠습니다.

 

1 ~ 4

// client/src/pages/shop

const onOrderRequest = () => {
  if (isMobile()) return mobileOrder();
  else return webOrder();
};

const newOrder = () => {
  return axios.get('/v1/inicis/new/order');
};

const webOrder = async () => {
  const { data: info } = await newOrder();
  const { status, data } = info;

  if (status === 'success') {
    orderNumber = data;

    axios.get('/v1/inicis/request/form', { params: { orderId: data } }).then(resolve => {
      const { data: info } = resolve;
      const { status, data } = info;
      
      if (status === 'success') {
        const form = document.createElement('form');
        form.method = 'post';
        form.acceptCharset = 'UTF-8';
        form.hidden = true;
        form.id = 'pay_form';

        for (let o in data) {
          const input = document.createElement('input');
          input.name = o;
          input.value = data[o];
          input.hidden = true;
          form.appendChild(input);
        }

        document.querySelector('#shop-page').appendChild(form);
        window.INIStdPay.pay('pay_form');
        inicisFormStatus = setInterval(checkInicisFormStatus, 1000);
      } else {
        alert('요청 실패');
      }
    });
  } else {
    alert('요청 실패');
  }
};

 

// server/api/controllers/inicis.controller.js

const getNewOrder = async (req, res) => {
  const { serviceId } = req.query; // 실제로 개발할때는 요청이 들어온 서비스 정보를 주문번호와 함께 저장합니다.

  try {
    const newOrderId = makeOrderId('sample');

    // save order Id
    // ...

    return res.send({ status: 'success', data: newOrderId });
  } catch (error) {
    console.log('create new order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};

const getRequestForm = async (req, res) => {
  const { orderId } = req.query;
  if (!orderId) {
    return res.send({ status: 'error', data: 'check parameters' });
  }

  try {
     // 요청된 orderId가 DB에 실제로 존재하는지 체크
  } catch (error) {
    console.log('check exist order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }

  try {
    const price = 100; // 실제로는 요청된 상품 정보를 조회
    const timestamp = dayjs().valueOf();

    const dataset = {
      version: '1.0',
      gopaymethod: 'VBank',
      mid: process.env.MID,
      signature: encryptSha256(`oid=${orderId}&price=${price}&timestamp=${timestamp}`),
      mKey: encryptSha256('SU5JTElURV9UUklQTEVERVNfS0VZU1RS'), // 개발용, 배포용에서는 발급된 key를 사용
      price,
      oid: orderId,
      timestamp,
      currency: 'WON',
      goodname: 'Sample',
      buyername: '홍길동',
      buyertel: '01012341234',
      buyeremail: 'sample@sample.com.kr',
      returnUrl: getServerDomain() + '/v1/inicis/pay/after',
      payViewType: 'popup',
      popupUrl: getServerDomain() + `/v1/inicis/popup/open/${orderId}`,
      closeUrl: '',
    };

    return res.send({ status: 'success', data: dataset });
  } catch (error) {
    console.log('make request form : ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};

이니시스의 웹 결제요청은 폼을 기본으로합니다.

폼을 기본으로 이니시스에서 제공하는 라이브러리를 통해

결제 API요청을 하기 때문에 위와 같은 처리가 필요합니다.

 

popup 형태로 결제 폼을 띄우기 위해서는 위 코드중

payViewType: 'popup',
popupUrl: getServerDomain() + `/v1/inicis/popup/open/${orderId}`

이 부분이 꼭 필요합니다.

팝업이 아니라 페이지 형태의 결제 폼을 제공한다면 위 두줄은 필요없습니다.

 

클라이언트에 띄워주는 팝업에

알맞는 결제모듈을 제공하기 위한 코드들을 살펴볼까요

 

5~7에 해당합니다.

// server/api/controller/inicis.controller.js

const openInicisModule = async (req, res) => {
  const { orderId } = req.params;
  if (!orderId) {
    return res.send({ status: 'error', data: 'check parameters' });
  }

  try {
    // 요청된 orderId가 DB에 실제로 존재하는지 체크
  } catch (error) {
    console.log('check exist order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }

  try {
    const price = 100; // 실제로는 요청된 상품 정보를 조회
    const timestamp = dayjs().valueOf();

    const dataset = {
      version: '1.0',
      gopaymethod: 'VBank',
      mid: process.env.MID,
      signature: encryptSha256(`oid=${orderId}&price=${price}&timestamp=${timestamp}`),
      mKey: encryptSha256('SU5JTElURV9UUklQTEVERVNfS0VZU1RS'), // 개발용, 배포용에서는 발급된 key를 사용
      price,
      oid: orderId,
      timestamp,
      currency: 'WON',
      goodname: 'Sample',
      buyername: '홍길동',
      buyertel: '01012341234',
      buyeremail: 'sample@sample.com.kr',
      returnUrl: getServerDomain() + '/v1/inicis/pay/after',
      payViewType: 'popup',
      popupUrl: getServerDomain() + `/v1/inicis/popup/open/${orderId}`,
      closeUrl: '',
    };

    return res.render('w_inicis', { ...dataset });
  } catch (error) {
    console.log('make request form : ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};
// server/views/w_inicis.pug

doctype html
html(lang="ko")
  head
    title= title
    meta(http-equiv="Content-Type", content="text/html;charset=UTF-8")
    meta(http-equiv="Cache-Control", content="no-cache")

    meta(property="og:title", content= '이니시스 결제')
    meta(property="og:description", content= '이니시스 결제 연동 해보자')
    meta(property="og:type" content= 'website')

    meta(name="description" content= '이니시스 결제 연동 해보자')
    meta(name="type" content= 'website')

    script(src="https://stgstdpay.inicis.com/stdjs/INIStdPay.js", type="text/javascript", charset="UTF-8")
  body
    script. 
      var onPay = function() { INIStdPay.pay("pay_form") }
      onPay()
    form(id="pay_form", method="post", accept-charset="UTF-8", hidden='true') 
      input(type="hidden", name="version", value= version)
      input(type="hidden", name="gopaymethod", value= gopaymethod)
      input(type="hidden", name="mid", value= mid)
      input(type="hidden", name="oid", value= oid)
      input(type="hidden", name="price", value= price)
      input(type="hidden", name="timestamp", value= timestamp)
      input(type="hidden", name="signature", value= signature)
      input(type="hidden", name="mKey", value= mKey)
      input(type="hidden", name="currency", value= currency)
      input(type="hidden", name="goodname", value= goodname)
      input(type="hidden", name="buyername", value= buyername)
      input(type="hidden", name="buyertel", value= buyertel)
      input(type="hidden", name="buyeremail", value= buyeremail)
      input(type="hidden", name="returnUrl", value= returnUrl)
      input(type="hidden", name="closeUrl", value=closeUrl)

 

결제 연동중 제일 중요한게 위 과정이라고 생각하는데요.

바로, 결제 모듈을 뷰 템플릿을 이용해 SSR(server-side rendering) 형태로 제공하는것입니다.

 

여기서 왜 어려움을 겪게되냐면

이니시스 API상에서 보면 "요청과 응답하는 서버의 Domain을 일치시켜라" 라는 부분이 있거든요.

CSR과 RestAPI를 통해 서비스를 제공하는 입장에선는

이 요구사항을 맞추기가 정말 어렵더라고요.

도대체 이걸 어떻게 해야되나... 많은 생각이 들었습니다.

 

그래서 생각해낸게 바로, "결제 모듈 페이지는 서버에서 제공하자!" 입니다.

CSR프로젝트라고해서 페이지를 모두 클라이언트에서

제공하려고 하는것은 나의 딱딱한 생각 때문구나! 싶었습니다.

 

나머지는 이제 원하는대로 요청을 처리하거나

실패/성공 페이지를 제공하는 정도입니다.

 

https://github.com/jaekwangLee/inicis-without-pg 를

참고하여 진행하면 그리 어렵지않아

웹 결제 플로우 코드리뷰는 여기까지로 마치도록 하겠습니다.

 

728x90
반응형
728x90

< 3탄 - 결제 플로우가 어떻게 되는가 >

 

이전 글들을 못보신 분들을 위해 앞선 글들의 링크를 제공합니다.

< 1탄 - 프롤로그 >

https://blog.self-made.cloud/240?category=752264 

< 2탄 - Code 먼저 공개합니다. >

https://blog.self-made.cloud/241?category=752264

 

 

이니시스 결제모듈은

웹 버전과 모바일 버전의 API가 각각 다르게 제공됩니다.

사용해보니 모바일 버전이 나중에 따로 제작된것같네요.

 

아무튼 제공되는 API가 다르니

두가지를 나눠서 설명할 예정입니다.

 

먼저, 웹 버전의 플로우는 이와같습니다.

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 폼을 서버로 요청
4. 서버에서 결제를 진행하기위한 데이터들을 클라이언트로 반환
5. 클라이언트에서 서버로 이니시스 결제모듈 요청 (팝업형태)
6. 서버에서 이니시스 결제모듈 반환
7. 클라이언트에 팝업 모듈이 열림 + 이때부터 팝업 모듈 상태를 주기적으로 체크
8. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  8-1. 닫은경우 팝업 열람전 페이지 유지 혹은 기타 처리
  8-2. 팝업에서 결제요청한 경우 서버로 요청이 전송됨 + 팝업이 닫힘
9. 서버에서 요청 받은 정보들을 통해 결제요청 처리 및 데이터 저장
10. 팝업 모듈이 닫힌게 체크되면 결제요청 완료 페이지로 이동
11. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌

 

모바일 버전의 플로우

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 모듈을 실행함
4. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  4-1. 팝업을 닫은 경우, 결제 실패 페이지로 보내거나, 기존의 페이지로 돌려보냄
  4-2. 결제 요청을 완료한 경우, 서버로 요청이 전송됨
5. 서버에서 요청을 처리 및 데이터를 저장하고 결제완료 페이지로 Redirect시킴
6. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌
728x90
반응형
728x90

< 2탄 - Code 먼저 공개합니다. >

 

필요한 정보를 위해

시리즈로 제공되는 블로그를 보는것은

어느정도 기본실력이 있는분들에게는

시간낭비 일수있습니다.

 

아임포트와 같은 서비스없이

이니시스 결제 연동을 하고자하는 분들 중에서

 

일부 코드만을 참고하시고자 하는 분들을 위해

관련 소스코드를 모두 업데이트해둔

공개 레포지토리를 먼저 공유합니다.

 

https://github.com/jaekwangLee/inicis-without-pg

 

GitHub - jaekwangLee/inicis-without-pg: CSR환경에서 PG사 없이 KG이니시스 결제연동을 합니다.

CSR환경에서 PG사 없이 KG이니시스 결제연동을 합니다. Contribute to jaekwangLee/inicis-without-pg development by creating an account on GitHub.

github.com

 

728x90
반응형
728x90

< 1탄 - 프롤로그 >

웹,앱 등의 서비스에서 광고 수익외에

직접적인 수익을 창출하려면 결제연동을 필수적으로 하게됩니다.

그 과정에서 어려움을 겪게되는대요.

 

주로 그 원인은

PG사들이 제공하는 가이드라인이

부실하기 때문입니다.

 

사실 부실할 수 밖에 없은 이유가있죠.

이제는 너무나도 다양해진 개발 환경에 

모두 대응되는 가이드라인을 제공할 수 없기 때문입니다.

 

이번에 React 프로젝트에 결제연동을 하다보니

react와 같은 CSR에 대해서는 가이드라인이 부실함은 물론

관련 자료도 매우 부족함을 알게되었습니다.

 

미약하지만

아임포트 같은 서비스없이

KG이니시스 결제모듈 연동에 성공하여 정보를 공유하고자합니다.

 

다룰 내용은

Client : React 

server : node js

PG: inicis
결제수단 : 가상계좌 ( 다른 결제수단도 PG사와 제휴만 맺으면 해당 방법으로 얼마든지 응용가능해보입니다.)

 

해당 상황에 해당하시는분들에게

많은 도움이 됐으면 합니다.

감사합니다.

728x90
반응형
728x90

올바른 개발자가 개발을 하다보면

자기의 입맛, 혹은 팀의 입맛에 맞게

개발에 도움이 되는 환경을 세팅해나가게 된다.

 

그러다보면 로컬에서는 적용되는데 서버에서는 뭔가 적용이 안되거나하는

경우가 종종 발생한다.

이번엔 그중에서 font 적용에 대해서 알아본다.

 

/MyApp
  /public
    /fonts
      honey-Bold.ttf
      honey-Bold.eot
      honey-Regular.ttf
      honey-Regular.eot
    /images
    index.html
  /src
    app.js
    index.css

 

자, 주어진 프로젝트 환경의 파일트리가 위와 같은 상황이다.

index.css에서 프로젝트 내 모든 텍스트의 폰트를 잡아주려고한다.

어떻게 하면될까?

 

// index.css

@font-face {
    font-family: 'honey';
    src: url('/public/fonts/honey-Bold.eot');
    src: url('/public/fonts/honey-Bold.eot') format('embedded-opentype'), url('/public/fonts/honey-Bold.ttf') format('truetype');
    font-weight: 600;
    font-style: normal;
}

@font-face {
    font-family: 'honey';
    src: url('/public/fonts/honey-Regular.eot');
    src: url('/public/fonts/honey-Regular.eot') format('embedded-opentype'), url('/public/fonts/honey-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
}

* {
    font-family: 'honey', sans-serif;
}

 

honey라는 폰트는 없겠지만 예를들면 이렇다.

woff 까지 이와 같은 방식으로 설정해주면 된다.

 

eject한 CRA 프로젝트라면 빌드 했을때,

dist폴더가 자동으로 생성되며

dist/ 폴더아래로 빌드된 파일이 들어가게되는데

여기서 부터 문제가 발생한다.

 

빌드해 서버에 업로드했더니 폰트가 먹지않는 이슈

 

왜그럴까?

dist/ 폴더에는 public/fonts/ 가 존재하지 않는다.

간단한 webpack 설정을 통해 경로를 맞춰주어야한다.

 

npm install file-loader --save-dev
npm install copy-webpack-plugin --save-dev

 

준비물은 위 두가지이다.

 

const CopyWebpackPlugin = require('copy-webpack-plugin');

...

module: {
  rules: [
    {
      test: /\.(woff|woff2|eot|ttf|otf)$/,
      use: [
        {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'fonts/',
            publicPath: '/public',
          }
        }
      ]
    }
  ]
},

...

plugins: [
  new CopyWebpackPlugin({
    patterns: [
      {
        from: 'public/fonts',
        to: 'publi/fonts',
      }
    ]
  })
],

핵심은 CopyWebpackPlugin 부분이다.

여기서 무슨 일이 일어나냐면

public/fonts/ 경로의 파일들은

dist 아래에도 똑같이 만들어주는것이다.

 

그러면 빌드후에도 css설정이 당연하게도 잘 들어맞는다.

 

728x90
반응형