본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 2. · 64 Views
실전 DApp 개발 환경 구축 완벽 가이드
React와 ethers.js를 활용한 DApp 개발 환경 구축부터 CI/CD 파이프라인까지, 블록체인 프론트엔드 개발의 전 과정을 체계적으로 다룹니다. 초급 개발자도 따라할 수 있도록 실무 중심으로 설명합니다.
목차
- React_ethers.js_프로젝트_설정
- Private_Chain_프론트엔드_연동
- 설정_파일_버전_관리
- 팀원_간_환경_공유_방법
- CI_CD_파이프라인_구성
- 개발_테스트_스테이징_환경_분리
1. React ethers.js 프로젝트 설정
김개발 씨는 회사에서 새로운 블록체인 프로젝트를 맡게 되었습니다. "DApp을 만들어야 하는데, 프론트엔드는 어떻게 구성해야 하지?" 막막한 마음으로 검색을 시작했지만, 정보가 너무 파편화되어 있어 어디서부터 시작해야 할지 감이 잡히지 않았습니다.
DApp 프론트엔드 개발의 첫 걸음은 React와 ethers.js를 연동하는 것입니다. ethers.js는 이더리움 블록체인과 통신하기 위한 라이브러리로, 마치 웹 개발에서 axios가 서버와 통신하는 것처럼 블록체인과 대화하는 역할을 합니다.
올바른 프로젝트 구조를 갖추면 이후 개발이 훨씬 수월해집니다.
다음 코드를 살펴봅시다.
// 프로젝트 생성 및 의존성 설치
// npx create-react-app my-dapp --template typescript
// npm install ethers @types/node
// src/config/web3.ts - Web3 설정 파일
import { ethers } from 'ethers';
// 프로바이더 타입 정의
export interface Web3Config {
provider: ethers.JsonRpcProvider;
chainId: number;
networkName: string;
}
// 기본 프로바이더 생성 함수
export const createProvider = (rpcUrl: string): ethers.JsonRpcProvider => {
return new ethers.JsonRpcProvider(rpcUrl);
};
// 환경별 RPC URL 설정
export const RPC_URLS = {
development: 'http://localhost:8545',
testnet: process.env.REACT_APP_TESTNET_RPC || '',
mainnet: process.env.REACT_APP_MAINNET_RPC || ''
};
김개발 씨는 입사 6개월 차 프론트엔드 개발자입니다. 그동안 일반적인 웹 서비스만 개발해왔는데, 이번에 회사에서 블록체인 기반 서비스를 론칭하기로 했습니다.
팀장님은 김개발 씨에게 DApp 프론트엔드 개발을 맡겼습니다. "DApp이라...
뭔가 어려울 것 같은데." 김개발 씨는 걱정이 앞섰습니다. 하지만 옆자리 선배 박시니어 씨가 다가와 말했습니다.
"걱정 마세요. 결국 프론트엔드는 프론트엔드예요.
다만 서버 대신 블록체인과 통신한다는 것만 다를 뿐이죠." 그렇다면 ethers.js란 정확히 무엇일까요? 쉽게 비유하자면, ethers.js는 마치 통역사와 같습니다.
우리가 외국에 가면 현지 언어를 몰라도 통역사가 있으면 의사소통이 가능하듯이, ethers.js가 있으면 복잡한 블록체인 프로토콜을 몰라도 자바스크립트로 블록체인과 대화할 수 있습니다. 트랜잭션을 보내고, 스마트 컨트랙트를 호출하고, 지갑을 연결하는 모든 작업을 ethers.js가 대신 처리해줍니다.
프로젝트를 시작할 때 가장 먼저 해야 할 일은 **프로바이더(Provider)**를 설정하는 것입니다. 프로바이더는 블록체인 네트워크에 접속하기 위한 연결 통로입니다.
마치 인터넷에 접속하려면 ISP(인터넷 서비스 제공자)가 필요한 것처럼, 블록체인에 접속하려면 프로바이더가 필요합니다. 위 코드에서 createProvider 함수를 살펴보겠습니다.
이 함수는 RPC URL을 받아서 JsonRpcProvider 인스턴스를 생성합니다. RPC URL은 블록체인 노드의 주소입니다.
개발 환경에서는 보통 localhost:8545를 사용하는데, 이는 로컬에서 실행 중인 가나슈(Ganache)나 하드햇(Hardhat) 노드를 가리킵니다. RPC_URLS 객체를 보면 환경별로 다른 URL을 설정해둔 것을 알 수 있습니다.
개발 환경에서는 로컬 노드를, 테스트넷에서는 세폴리아(Sepolia) 같은 테스트 네트워크를, 메인넷에서는 실제 이더리움 네트워크를 사용합니다. 이렇게 환경을 분리해두면 실수로 메인넷에 테스트 트랜잭션을 보내는 사고를 방지할 수 있습니다.
실제 현업에서는 Infura나 Alchemy 같은 노드 서비스를 많이 사용합니다. 직접 노드를 운영하려면 상당한 인프라 비용과 관리 노력이 필요하기 때문입니다.
이런 서비스들은 API 키만 발급받으면 바로 블록체인에 접속할 수 있게 해줍니다. 주의할 점이 있습니다.
환경 변수에 저장된 RPC URL이나 API 키는 절대로 코드에 하드코딩하면 안 됩니다. 특히 프라이빗 키나 민감한 정보는 더욱 그렇습니다. .env 파일을 사용하고, 이 파일은 반드시 .gitignore에 추가해야 합니다.
박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "생각보다 복잡하지 않네요.
결국 설정 파일을 잘 구성하는 게 핵심이군요!" 이제 본격적인 DApp 개발을 시작할 준비가 되었습니다.
실전 팁
💡 - ethers.js v6부터 문법이 많이 바뀌었으니 버전을 꼭 확인하세요
- 개발 초기에 TypeScript 타입 정의를 철저히 해두면 나중에 디버깅 시간이 크게 줄어듭니다
2. Private Chain 프론트엔드 연동
김개발 씨가 프로젝트 설정을 마치고 기능 개발을 시작하려는데, 박시니어 씨가 물었습니다. "테스트용 프라이빗 체인은 세팅해뒀어요?" 김개발 씨는 멈칫했습니다.
실제 네트워크에서 테스트하면 가스비도 들고 시간도 오래 걸린다는 건 알았지만, 프라이빗 체인을 어떻게 연동해야 하는지는 몰랐습니다.
프라이빗 체인은 개발과 테스트를 위한 로컬 블록체인 환경입니다. 마치 개발 서버를 로컬에 띄워놓고 작업하는 것처럼, 블록체인도 로컬에서 실행할 수 있습니다.
Hardhat이나 Ganache를 사용하면 몇 분 만에 나만의 블록체인을 만들 수 있습니다.
다음 코드를 살펴봅시다.
// src/hooks/useWeb3.ts - Web3 연결 커스텀 훅
import { useState, useEffect, useCallback } from 'react';
import { ethers, BrowserProvider, JsonRpcSigner } from 'ethers';
interface UseWeb3Return {
provider: BrowserProvider | null;
signer: JsonRpcSigner | null;
account: string | null;
chainId: number | null;
connect: () => Promise<void>;
isConnecting: boolean;
}
export const useWeb3 = (): UseWeb3Return => {
const [provider, setProvider] = useState<BrowserProvider | null>(null);
const [signer, setSigner] = useState<JsonRpcSigner | null>(null);
const [account, setAccount] = useState<string | null>(null);
const [chainId, setChainId] = useState<number | null>(null);
const [isConnecting, setIsConnecting] = useState(false);
const connect = useCallback(async () => {
if (!window.ethereum) {
alert('MetaMask를 설치해주세요!');
return;
}
setIsConnecting(true);
try {
const browserProvider = new BrowserProvider(window.ethereum);
const userSigner = await browserProvider.getSigner();
const userAddress = await userSigner.getAddress();
const network = await browserProvider.getNetwork();
setProvider(browserProvider);
setSigner(userSigner);
setAccount(userAddress);
setChainId(Number(network.chainId));
} finally {
setIsConnecting(false);
}
}, []);
return { provider, signer, account, chainId, connect, isConnecting };
};
김개발 씨는 프라이빗 체인이라는 말을 처음 들었습니다. "프라이빗 체인이요?
그게 뭔가요?" 박시니어 씨가 웃으며 설명했습니다. "실제 이더리움 네트워크에서 테스트하면 매번 가스비를 내야 하잖아요.
그리고 트랜잭션 확정까지 시간도 걸리고요. 프라이빗 체인은 내 컴퓨터에서 돌아가는 가짜 블록체인이에요." 프라이빗 체인은 마치 게임의 연습 모드와 같습니다.
실제 대회에 나가기 전에 연습 모드에서 충분히 훈련하듯이, 실제 메인넷에 배포하기 전에 프라이빗 체인에서 충분히 테스트하는 것입니다. 가스비도 무료이고, 트랜잭션도 즉시 처리됩니다.
가장 많이 사용하는 도구는 Hardhat입니다. Hardhat은 이더리움 개발을 위한 종합 툴킷으로, 로컬 노드 실행, 스마트 컨트랙트 컴파일, 테스트, 배포까지 모든 것을 지원합니다.
터미널에서 npx hardhat node 명령어 하나면 즉시 로컬 블록체인이 실행됩니다. 위 코드의 useWeb3 훅을 살펴보겠습니다.
이 훅은 React 애플리케이션에서 블록체인 연결을 관리합니다. connect 함수가 호출되면 MetaMask와 연결하고, 사용자의 지갑 주소와 현재 네트워크 정보를 가져옵니다.
BrowserProvider는 MetaMask 같은 브라우저 지갑과 통신하기 위한 프로바이더입니다. window.ethereum 객체를 통해 MetaMask에 접근합니다.
사용자가 지갑 연결을 승인하면 Signer 객체를 얻을 수 있는데, 이 Signer로 트랜잭션에 서명할 수 있습니다. 실무에서 중요한 것은 chainId 검증입니다.
사용자가 잘못된 네트워크에 연결되어 있으면 예상치 못한 문제가 발생할 수 있습니다. 예를 들어, 개발자는 테스트넷을 기대하는데 사용자의 MetaMask가 메인넷에 연결되어 있다면 실제 자산이 이동할 수 있습니다.
프라이빗 체인을 사용하려면 MetaMask에 네트워크를 추가해야 합니다. 네트워크 이름은 자유롭게 설정하고, RPC URL은 http://localhost:8545, 체인 ID는 31337(Hardhat 기본값)을 입력합니다.
이렇게 하면 로컬에서 실행 중인 Hardhat 노드와 통신할 수 있습니다. 한 가지 주의할 점이 있습니다.
프라이빗 체인을 재시작하면 계정의 nonce가 초기화됩니다. MetaMask는 이전 nonce를 기억하고 있어서 트랜잭션이 실패할 수 있습니다. 이럴 때는 MetaMask 설정에서 '계정 초기화'를 해주면 됩니다.
김개발 씨가 직접 Hardhat 노드를 실행하고 MetaMask를 연결해보니, 정말로 가스비 걱정 없이 마음껏 테스트할 수 있었습니다. "이거 완전 신세계네요!" 개발 속도가 눈에 띄게 빨라졌습니다.
실전 팁
💡 - Hardhat 노드 실행 시 --fork 옵션으로 메인넷 상태를 복제하면 실제 컨트랙트와 상호작용 테스트가 가능합니다
- MetaMask에서 'Nonce 재설정' 기능을 알아두면 프라이빗 체인 재시작 시 유용합니다
3. 설정 파일 버전 관리
개발이 순조롭게 진행되던 어느 날, 김개발 씨는 실수로 .env 파일을 Git에 커밋해버렸습니다. 다행히 테스트넷 API 키였지만, 박시니어 씨는 심각한 표정으로 말했습니다.
"만약 메인넷 프라이빗 키였다면 어떻게 됐을까요? 설정 파일 관리는 보안의 기본입니다."
블록체인 프로젝트에서 설정 파일 관리는 일반 웹 프로젝트보다 훨씬 중요합니다. 프라이빗 키가 유출되면 자산을 영구적으로 잃을 수 있기 때문입니다.
환경 변수 분리, .gitignore 설정, 설정 템플릿 관리까지 체계적인 접근이 필요합니다.
다음 코드를 살펴봅시다.
// .env.example - 템플릿 파일 (Git에 커밋)
REACT_APP_ENVIRONMENT=development
REACT_APP_LOCAL_RPC=http://localhost:8545
REACT_APP_TESTNET_RPC=your_testnet_rpc_url_here
REACT_APP_MAINNET_RPC=your_mainnet_rpc_url_here
REACT_APP_CONTRACT_ADDRESS=your_contract_address_here
// src/config/environment.ts - 환경 설정 타입 안전하게 관리
type Environment = 'development' | 'testnet' | 'mainnet';
interface EnvironmentConfig {
rpcUrl: string;
contractAddress: string;
blockExplorer: string;
}
const configs: Record<Environment, EnvironmentConfig> = {
development: {
rpcUrl: process.env.REACT_APP_LOCAL_RPC || 'http://localhost:8545',
contractAddress: process.env.REACT_APP_DEV_CONTRACT || '',
blockExplorer: ''
},
testnet: {
rpcUrl: process.env.REACT_APP_TESTNET_RPC || '',
contractAddress: process.env.REACT_APP_TESTNET_CONTRACT || '',
blockExplorer: 'https://sepolia.etherscan.io'
},
mainnet: {
rpcUrl: process.env.REACT_APP_MAINNET_RPC || '',
contractAddress: process.env.REACT_APP_MAINNET_CONTRACT || '',
blockExplorer: 'https://etherscan.io'
}
};
const currentEnv = (process.env.REACT_APP_ENVIRONMENT || 'development') as Environment;
export const config = configs[currentEnv];
김개발 씨의 실수는 많은 개발자들이 한 번쯤 경험하는 일입니다. 특히 블록체인 프로젝트에서는 이런 실수가 치명적일 수 있습니다.
일반 웹 서비스에서 API 키가 유출되면 키를 재발급받으면 되지만, 블록체인에서 프라이빗 키가 유출되면 해당 지갑의 모든 자산을 잃을 수 있습니다. 박시니어 씨는 화이트보드에 설정 파일 관리 원칙을 적기 시작했습니다. "첫 번째 원칙은 민감한 정보는 절대 코드에 포함하지 않는다입니다." 환경 변수를 관리하는 표준 방법은 .env 파일을 사용하는 것입니다.
React 프로젝트에서는 REACT_APP_ 접두사가 붙은 환경 변수만 브라우저에서 접근할 수 있습니다. 하지만 이 파일은 절대로 Git에 커밋하면 안 됩니다.
대신 .env.example 파일을 만들어 템플릿으로 사용합니다. 이 파일에는 실제 값 대신 "your_api_key_here" 같은 플레이스홀더를 넣습니다.
새로운 팀원이 합류하면 이 파일을 복사해서 .env로 이름을 바꾸고 실제 값을 채워넣으면 됩니다. 위 코드의 environment.ts 파일을 보면 타입 안전하게 환경 설정을 관리하는 방법을 알 수 있습니다.
Environment 타입을 정의하고, 각 환경별 설정을 객체로 관리합니다. 이렇게 하면 오타로 인한 버그를 방지할 수 있고, 새로운 환경을 추가할 때도 타입 체커가 누락된 설정을 알려줍니다.
.gitignore 파일 설정도 매우 중요합니다. 최소한 다음 항목들은 반드시 포함해야 합니다: .env, .env.local, .env.*.local, 그리고 node_modules.
블록체인 프로젝트에서는 추가로 hardhat의 cache 폴더, artifacts 폴더, 그리고 개인 키가 저장될 수 있는 모든 파일을 제외해야 합니다. 한 가지 더 중요한 것이 있습니다.
Git 히스토리에 한 번이라도 민감한 정보가 포함되었다면, 단순히 삭제 커밋을 해도 히스토리에 남아있습니다. 이런 경우 git filter-branch나 BFG Repo-Cleaner 같은 도구로 히스토리를 정리하거나, 최악의 경우 해당 키를 폐기하고 새로 발급받아야 합니다. "설정 관리가 이렇게 중요한 거였군요." 김개발 씨는 깊이 반성했습니다.
박시니어 씨가 덧붙였습니다. "보안은 불편함을 감수하는 거예요.
처음엔 번거롭지만, 습관이 되면 자연스러워집니다."
실전 팁
💡 - Git 커밋 전에 git diff --staged로 민감한 정보가 포함되어 있지 않은지 확인하는 습관을 들이세요
- pre-commit 훅을 사용해 .env 파일이 커밋되는 것을 자동으로 방지할 수 있습니다
4. 팀원 간 환경 공유 방법
프로젝트가 커지면서 새로운 팀원 이주니어 씨가 합류했습니다. "환경 설정하는 데 하루 종일 걸렸어요..." 이주니어 씨의 하소연을 들은 박시니어 씨는 고개를 저었습니다.
"우리 온보딩 문서가 부실했네요. 환경 공유 방법을 체계화해야겠어요."
팀 프로젝트에서 환경 설정 공유는 생산성의 핵심입니다. 새로운 팀원이 합류할 때마다 설정에 하루를 소비한다면 그만큼 개발 시간이 줄어듭니다.
문서화, 스크립트 자동화, 시크릿 관리 도구를 활용하면 온보딩 시간을 획기적으로 줄일 수 있습니다.
다음 코드를 살펴봅시다.
// scripts/setup.sh - 환경 초기 설정 스크립트
#!/bin/bash
echo "DApp 개발 환경을 설정합니다..."
# 의존성 설치
echo "1. 의존성 설치 중..."
npm install
# 환경 변수 파일 생성
if [ ! -f .env ]; then
echo "2. .env 파일 생성 중..."
cp .env.example .env
echo " .env 파일이 생성되었습니다. 실제 값을 입력해주세요."
fi
# 스마트 컨트랙트 컴파일
echo "3. 스마트 컨트랙트 컴파일 중..."
npx hardhat compile
# 타입 생성 (typechain 사용 시)
echo "4. 컨트랙트 타입 생성 중..."
npx hardhat typechain
echo "설정 완료! 다음 단계:"
echo "1. .env 파일에 실제 API 키와 RPC URL을 입력하세요"
echo "2. npm run dev로 개발 서버를 시작하세요"
// package.json scripts 섹션
{
"scripts": {
"setup": "chmod +x scripts/setup.sh && ./scripts/setup.sh",
"dev": "react-scripts start",
"build": "react-scripts build",
"node:local": "npx hardhat node",
"deploy:local": "npx hardhat run scripts/deploy.ts --network localhost"
}
}
이주니어 씨의 고충은 모든 신입 개발자가 겪는 일입니다. "이 라이브러리 버전이 안 맞아요", "이 환경 변수는 어디서 가져와요?", "컴파일이 안 돼요" 등등.
환경 설정에 시간을 쏟다 보면 정작 코드를 작성할 시간이 부족해집니다. 박시니어 씨는 팀 미팅에서 제안했습니다.
"환경 설정을 원 커맨드 셋업으로 만들어봅시다. 새 팀원이 와도 명령어 하나로 바로 개발을 시작할 수 있게요." 위 코드의 setup.sh 스크립트가 바로 그 해결책입니다.
이 스크립트는 순서대로 의존성 설치, 환경 변수 파일 생성, 스마트 컨트랙트 컴파일, 타입 생성까지 모든 초기 설정을 자동으로 수행합니다. 새 팀원은 npm run setup 명령어 하나만 실행하면 됩니다.
환경 변수 공유에서 가장 중요한 것은 민감한 정보와 그렇지 않은 정보를 분리하는 것입니다. 로컬 개발용 설정은 .env.example에 기본값을 넣어둘 수 있지만, 테스트넷이나 메인넷 API 키는 별도로 공유해야 합니다.
팀에서 시크릿을 안전하게 공유하는 방법은 여러 가지가 있습니다. 1Password, HashiCorp Vault, AWS Secrets Manager 같은 시크릿 관리 도구를 사용하는 것이 가장 안전합니다.
Slack이나 카카오톡으로 API 키를 공유하는 것은 절대 금물입니다. README 파일에 상세한 온보딩 가이드를 작성하는 것도 중요합니다.
필요한 도구 목록(Node.js 버전, MetaMask 설치 등), 설정 순서, 자주 발생하는 문제와 해결 방법을 문서화해두면 질문 응대 시간을 크게 줄일 수 있습니다. Docker를 활용하면 환경 차이로 인한 문제를 근본적으로 해결할 수 있습니다.
개발 환경 전체를 Docker 이미지로 만들어두면 "제 컴퓨터에서는 되는데요"라는 말을 더 이상 듣지 않아도 됩니다. 특히 Hardhat 노드를 Docker로 실행하면 팀 전체가 동일한 블록체인 상태에서 테스트할 수 있습니다.
한 달 후, 또 다른 신입 개발자가 합류했습니다. 이번에는 30분 만에 환경 설정을 끝내고 바로 코드를 작성하기 시작했습니다.
"진작에 이렇게 할 걸 그랬네요." 박시니어 씨가 흐뭇하게 웃었습니다.
실전 팁
💡 - README에 '빠른 시작' 섹션을 만들어 3단계 이내로 개발을 시작할 수 있게 하세요
- 주요 환경 변수에는 설명 주석을 달아두면 혼란을 줄일 수 있습니다
5. CI CD 파이프라인 구성
수동 배포를 하던 어느 날, 김개발 씨가 실수로 테스트를 돌리지 않고 배포해버렸습니다. 결과는 참담했습니다.
프로덕션에서 버그가 터졌고, 급하게 롤백해야 했습니다. "이런 실수가 반복되면 안 되겠어요.
CI/CD를 도입합시다." 팀장님의 결정이 내려졌습니다.
CI/CD는 코드 변경 사항을 자동으로 테스트하고 배포하는 파이프라인입니다. 마치 공장의 자동화 생산 라인처럼, 코드가 커밋되면 테스트, 빌드, 배포가 자동으로 진행됩니다.
사람의 실수를 줄이고 배포 속도를 높이는 핵심 인프라입니다.
다음 코드를 살펴봅시다.
// .github/workflows/ci.yml - GitHub Actions CI 설정
name: DApp CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Compile contracts
run: npx hardhat compile
- name: Run contract tests
run: npx hardhat test
- name: Run frontend tests
run: npm run test -- --coverage --watchAll=false
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install and build
run: |
npm ci
npm run build
env:
REACT_APP_ENVIRONMENT: testnet
REACT_APP_TESTNET_RPC: ${{ secrets.TESTNET_RPC_URL }}
김개발 씨의 실수는 수동 배포의 전형적인 문제입니다. 아무리 꼼꼼한 개발자라도 반복 작업에서 실수는 피할 수 없습니다.
**CI/CD(Continuous Integration/Continuous Deployment)**는 이런 인간적인 실수를 자동화로 방지하는 시스템입니다. **CI(지속적 통합)**는 마치 품질 검사원과 같습니다.
코드가 저장소에 올라올 때마다 자동으로 테스트를 실행해서 문제가 있으면 즉시 알려줍니다. **CD(지속적 배포)**는 품질 검사를 통과한 제품을 자동으로 출하하는 것과 같습니다.
위 코드는 GitHub Actions를 사용한 CI 파이프라인입니다. GitHub Actions는 무료로 사용할 수 있고, GitHub와 완벽하게 통합되어 있어서 많은 프로젝트에서 사용됩니다.
파이프라인의 흐름을 살펴보겠습니다. 먼저 test 잡이 실행됩니다.
여기서는 린터로 코드 스타일을 검사하고, 스마트 컨트랙트를 컴파일하고, 컨트랙트 테스트와 프론트엔드 테스트를 실행합니다. 어느 하나라도 실패하면 파이프라인 전체가 실패합니다.
test 잡이 성공하면 build 잡이 실행됩니다. needs: test 설정이 이 의존 관계를 정의합니다.
빌드 단계에서는 환경 변수를 주입해서 프로덕션용 빌드를 생성합니다. 주목할 점은 **시크릿(${{ secrets.TESTNET_RPC_URL }})**을 사용해서 민감한 정보를 안전하게 주입한다는 것입니다.
블록체인 프로젝트의 CI에서 특히 중요한 것은 스마트 컨트랙트 테스트입니다. 컨트랙트는 한 번 배포하면 수정할 수 없기 때문에, 배포 전에 철저한 테스트가 필수입니다.
단위 테스트뿐만 아니라 포크 테스트(실제 네트워크 상태를 복제해서 테스트)도 고려해야 합니다. Pull Request 단계에서 테스트를 강제하는 것도 중요합니다.
GitHub의 Branch Protection 규칙을 설정하면 테스트가 통과하지 않은 PR은 병합할 수 없게 할 수 있습니다. 이렇게 하면 버그가 있는 코드가 메인 브랜치에 들어가는 것을 원천 차단할 수 있습니다.
한 달 후, 팀의 배포 프로세스가 완전히 바뀌었습니다. "요즘은 맘 편히 퇴근해요.
테스트가 알아서 잡아주니까요." 김개발 씨가 웃으며 말했습니다.
실전 팁
💡 - 테스트 시간이 길어지면 병렬 실행(matrix strategy)을 고려하세요
- 중요한 브랜치에는 반드시 Branch Protection 규칙을 설정하세요
6. 개발 테스트 스테이징 환경 분리
CI/CD가 잘 돌아가기 시작하자 새로운 문제가 생겼습니다. "이 기능, 개발 환경에서는 잘 되는데 테스트넷에서는 왜 안 되죠?" 환경마다 동작이 달라서 혼란스러웠습니다.
박시니어 씨가 말했습니다. "환경을 제대로 분리하지 않아서 그래요.
각 환경의 역할을 명확히 해야 합니다."
환경 분리는 안정적인 서비스 운영의 기본입니다. 개발 환경에서 마음껏 실험하고, 테스트 환경에서 QA를 진행하고, 스테이징에서 최종 점검 후 프로덕션에 배포합니다.
각 환경은 독립적이어야 하며, 명확한 승격 프로세스를 따라야 합니다.
다음 코드를 살펴봅시다.
// src/config/networks.ts - 네트워크별 설정
export const NETWORKS = {
localhost: {
chainId: 31337,
name: 'Localhost',
rpcUrl: 'http://localhost:8545',
contracts: {
token: '0x5FbDB2315678afecb367f032d93F642f64180aa3'
}
},
sepolia: {
chainId: 11155111,
name: 'Sepolia Testnet',
rpcUrl: process.env.REACT_APP_SEPOLIA_RPC || '',
contracts: {
token: process.env.REACT_APP_SEPOLIA_TOKEN || ''
},
blockExplorer: 'https://sepolia.etherscan.io'
},
mainnet: {
chainId: 1,
name: 'Ethereum Mainnet',
rpcUrl: process.env.REACT_APP_MAINNET_RPC || '',
contracts: {
token: process.env.REACT_APP_MAINNET_TOKEN || ''
},
blockExplorer: 'https://etherscan.io'
}
} as const;
// 현재 환경에 따른 네트워크 선택
export const getNetworkConfig = () => {
const env = process.env.REACT_APP_ENVIRONMENT || 'localhost';
const network = NETWORKS[env as keyof typeof NETWORKS];
if (!network) {
throw new Error(`Unknown environment: ${env}`);
}
return network;
};
// 네트워크 전환 헬퍼 함수
export const switchNetwork = async (chainId: number) => {
if (!window.ethereum) return;
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${chainId.toString(16)}` }]
});
} catch (error: any) {
if (error.code === 4902) {
console.log('네트워크를 추가해야 합니다');
}
}
};
"환경 분리가 왜 중요한가요?" 이주니어 씨가 물었습니다. 박시니어 씨가 대답했습니다.
"상상해보세요. 개발 중인 기능을 실수로 메인넷에 배포한다면?
또는 테스트 데이터가 실제 사용자에게 보인다면? 환경 분리는 이런 재앙을 막아줍니다." 일반적으로 네 가지 환경을 구성합니다.
개발(Development) 환경은 개발자가 자유롭게 실험하는 공간입니다. 버그가 있어도 괜찮고, 데이터가 망가져도 괜찮습니다.
테스트(Test) 환경은 QA팀이 기능을 검증하는 공간입니다. 스테이징(Staging) 환경은 프로덕션과 동일한 설정으로 최종 점검을 수행합니다.
**프로덕션(Production)**은 실제 사용자가 접하는 환경입니다. 블록체인 프로젝트에서는 이 구분이 더욱 중요합니다.
각 환경이 다른 네트워크를 사용하기 때문입니다. 개발 환경은 로컬 Hardhat 노드, 테스트 환경은 세폴리아 같은 테스트넷, 프로덕션은 이더리움 메인넷을 사용합니다.
위 코드의 NETWORKS 객체를 보면 각 환경별로 필요한 모든 정보가 정의되어 있습니다. chainId, 네트워크 이름, RPC URL, 그리고 배포된 컨트랙트 주소까지.
환경 변수(REACT_APP_ENVIRONMENT)에 따라 적절한 설정이 자동으로 선택됩니다. getNetworkConfig 함수는 현재 환경에 맞는 네트워크 설정을 반환합니다.
알 수 없는 환경이 설정되어 있으면 에러를 던져서 조기에 문제를 발견할 수 있게 합니다. 이렇게 실패를 빠르게(fail fast) 하는 것이 좋은 설계입니다.
switchNetwork 함수는 사용자의 MetaMask를 올바른 네트워크로 전환시킵니다. DApp에 접속했는데 사용자가 다른 네트워크에 연결되어 있다면, 이 함수로 자동 전환을 유도할 수 있습니다.
에러 코드 4902는 해당 네트워크가 MetaMask에 추가되어 있지 않다는 의미입니다. 실무에서는 환경별 배포 파이프라인도 분리합니다.
develop 브랜치에 머지되면 테스트넷에 자동 배포하고, main 브랜치에 머지되면 메인넷에 배포하는 식입니다. 단, 메인넷 배포는 자동이 아니라 수동 승인을 거치는 것이 안전합니다.
"이제 이해됐어요. 환경마다 역할이 다르고, 그에 맞는 설정과 프로세스가 필요한 거군요." 이주니어 씨가 고개를 끄덕였습니다.
체계적인 환경 분리는 안정적인 서비스 운영의 첫걸음입니다.
실전 팁
💡 - 스테이징 환경은 프로덕션과 최대한 동일하게 구성하세요. 설정 차이가 버그의 원인이 됩니다
- 각 환경에 시각적 구분(헤더 색상, 환경 뱃지 등)을 추가하면 실수를 방지할 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
UX와 협업 패턴 완벽 가이드
AI 에이전트와 사용자 간의 효과적인 협업을 위한 UX 패턴을 다룹니다. 프롬프트 핸드오프부터 인터럽트 처리까지, 현대적인 에이전트 시스템 설계의 핵심을 배웁니다.
빌드와 배포 자동화 완벽 가이드
Flutter 앱 개발에서 GitHub Actions를 활용한 CI/CD 파이프라인 구축부터 앱 스토어 자동 배포까지, 초급 개발자도 쉽게 따라할 수 있는 빌드 자동화의 모든 것을 다룹니다.
CI/CD 파이프라인 통합 완벽 가이드
Jenkins, GitLab CI와 Ansible을 연동하여 자동화된 배포 파이프라인을 구축하는 방법을 다룹니다. Ansible Tower/AWX의 활용법과 실무에서 바로 적용 가능한 워크플로우 설계 패턴을 단계별로 설명합니다.
자가 치유 및 재시도 패턴 완벽 가이드
AI 에이전트와 분산 시스템에서 필수적인 자가 치유 패턴을 다룹니다. 에러 감지부터 서킷 브레이커까지, 시스템을 스스로 복구하는 탄력적인 코드 작성법을 배워봅니다.
Feedback Loops 컴파일러와 CI/CD 완벽 가이드
컴파일러 피드백 루프부터 CI/CD 파이프라인, 테스트 자동화, 자가 치유 빌드까지 현대 개발 워크플로우의 핵심을 다룹니다. 초급 개발자도 쉽게 이해할 수 있도록 실무 예제와 함께 설명합니다.