한동안 Vue.js나 Nuxt.js만 써 오다가, 최근 들어 리액트 프로젝트에 투입되면서
다시금 초심으로 돌아가 React.js를 공부해보자 라는 생각이 들었다. (첫 프론트엔드 입문을 React.js로 했었음.)
그래서, 몇 년 전에 해본거긴 한데 기억을 더듬어가며 개인연습 겸 겸사겸사 포트폴리오 겸 쇼핑몰 만들기를 해보려고 함.
이왕 하는김에 지금 진행중인 프로젝트와 비슷한 레이아웃 구성으로 가보려고 한다. (창의성이 오래전에 고갈되었는지... 독창적인게 생각 안난다 ㅠ)
🛠️ 사용 기술 스택 정리
💻 프론트엔드
- React : 컴포넌트 기반 UI 개발
- Vite : 빠른 빌드 및 핫 리로딩을 제공
- TypeScript : 정적 타입 기반 개발로 안정성 향상
🎨 스타일링
- Emotion (@emotion/react, @emotion/styled) : CSS-in-JS 방식의 스타일링 라이브러리. 컴포넌트 스타일링하기 편함.
🔐 백엔드 (예정 - Firebase / 진행중 변동될 수도 있음)
- Firebase Authentication : 이메일/비밀번호 로그인, 구글/깃허브 등 소셜 로그인 지원
- Firebase Firestore : NoSQL방식의 실시간 데이터베이스 (유저 정보 저장에 적합)
- Firebase Security Rules : 인증된 사용자만 데이터 접근 가능하도록 보안규칙 설정
- Firebase Hosting : 정적파일 배포
- Firebase SDK : 클라이언트에서 Firebase 서비스 호출 (로그인/데이터 저장 등)
예전엔 Firebase로 안했던것같은데 이번엔 한번 써보려고 함.
✨ Firebase를 쓰면 좋은 이유
- 직접 서버를 만들 필요 없어 개발이 빨라짐.
- 인증, DB, 호스팅, 파일 업로드까지 다 지원됨.
- 무료 플랜 사용가능
- 프론트 중심 개발자 입장에선 사용하기 아무래도 편함.
복잡하게 썼지만, 쉽게 말해 React + TS + Emotion 으로 쇼핑몰을 만들어보겠다는 뜻임.
### 대충 구상해 본 디렉토리 구성
shopmall/
├── public/ # 정적 파일 (import필요없이 사용할 favicon, robots.txt이나 mp4영상파일 등)
├── src/
│ ├── assets/ # 이미지, 폰트 등 번들링 및 import해서 사용할 리소스들
│ ├── components/ # 재사용 가능한 UI 컴포넌트들 (버튼, 카드, 모달 등)
│ ├── features/ # 도메인 별 컴포넌트 묶음 (상품, 장바구니, 회원 등)
│ │ ├── product/
│ │ │ ├── ProductCard.tsx
│ │ │ ├── ProductList.tsx
│ │ │ └── productSlice.ts (상태관리 시)
│ │ ├── cart/
│ │ └── user/
│ ├── hooks/ # 커스텀 훅들
│ ├── layouts/ # 페이지 레이아웃 컴포넌트 (헤더, 푸터 등 포함)
│ ├── pages/ # 라우팅용 페이지 컴포넌트 (메인, 상세, 로그인 등)
│ ├── styles/ # Emotion 글로벌 스타일, 테마 등
│ ├── utils/ # 유틸 함수, API 함수, 상수 등
│ ├── App.tsx
│ ├── main.tsx
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md
간단히 스케치해 본 디렉토리는 다음과 같음. 스케치일 뿐이라 빼먹은것도 있을수도 있고 작업하면서 달라질 수도 있음.
styles폴더 안에 스타일 tsx들을 따로 모아두는게 더 좋을지, 각 페이지 tsx와 같은 루트에 나란히 두는게 나을지는 모르겠는데,
일단 프로젝트 기초 세팅 하면서 손 잡히는대로 해 보겠음.
이 포스팅을 마칠 즈음에 디렉토리 구성을 캡쳐해 올리겠음.
React 오랜만에 하려니 막막하다... ㅅㅂ 어떻게 하더라 ㅋㅋ
(일단 Node.js정도는 다 설치했다고 생각하고 시작함. 모르겠으면 터미널에 node -v 쳐봐라.)
(1) React 프로젝트 생성하기
Vite도 같이 사용해보겠다고 했으니까, create-react-app 말고 다른걸 입력해보겠다.
npm create vite@latest shopmall -- --template react
shopmall은 내가 스스로 정한 내 프로젝트 이름임. 저기에 shopmall말고 my-shop, myprj 등 뭐 다양하게 맘대로 입력해도 됨.
엥? SWC는 또 뭐임
진짜 프론트 단은 자고 일어나면 뭐가 바뀌어 있다 ㅠㅠ 맨날 공부해야 함
⚙️ TypeScript vs TypeScript + SWC
항목 | 설명 |
TypeScript | 일반적인 TypeScript 설정 (Vite + TS) |
TypeScript + SWC | TypeScript를 SWC(빠른 컴파일러) 로 변환 |
검색해 보니 이런 차이가 있더라
🧠 SWC란?
- Speedy Web Compiler의 약자. Rust로 만든 초고속 트랜스파일러로, 기존 Babel보다 훨씬 빠름
- Facebook의 Babel vs SWC = JavaScript로 된 것 vs Rust로 된 것.
- 성능이 최대 20배 빠르다는 말도 있고...?
뭐 일단 SWC란 녀석은 Vite와 궁합이 매우 좋아보인다. 낯설지만, Pick 안 할 이유는 없다고 판단했다. TypeScript + SWC로 ㄱㄱ
프로젝트 생성했으니, 초심자처럼 바로 npm i 때리지 말고 cd로 경로 바꿔주는거 잊지말자.
cd shopmall # 생성한 프로젝트 폴더로 이동
npm install # npm 설치
몇초 안걸려서 인스톨은 다 끝남. run dev로 돌려보자.
npm run dev
localhost정보 뜨는거 복사해 크롬에 붙여넣어 들어가 보면
성공적으로 기본 React 템플릿 페이지가 뜨고 있음.
※ 참고로, TailwindCSS 설치할거냐고 뜨는데 난 안설치함. 어차피 Emotion으로 할거기도하고, 무엇보다 언제부턴가 Tailwind 설치하면 자꾸 오류 뜨는데 왜 이러는지 모르겠음. 설치는 됐는데 node_modules 안에도 존재하지 않아서 못불러오고... 암튼 Tailwind는 패스하겠음.
이제 Emotion을 설치.
npm install @emotion/react @emotion/styled
TS환경이니까 타입패키지도 같이 설치하겠다.
npm install -D @types/react
이제 쇼핑몰의 구성을 위해, 카테고리나 컴포넌트들의 구조를 조금 디테일하게 스케치 해 보겠음.
src/
├── components/
│ ├── KeyVisual.tsx # 메인 키비쥬얼 (영상 혹은 슬라이더)
│ ├── QuickMenu.tsx # 퀵버튼 메뉴 (아이콘 + 텍스트)
│ ├── BannerSection.tsx # 프로모션 배너 영역
│ ├── ProductList.tsx # 상품 리스트 공통 컴포넌트
│ ├── ProductCard.tsx # 상품 카드 UI
│ └── SpecialPromotion.tsx # 기획전 영역
├── pages/
│
└── main.tsx # 메인 페이지, 위 컴포넌트 조합
난 main.tsx를 진짜 메인페이지처럼 사용하고 싶다.
그렇다면 React 앱 진입점 포인트는 어디에 만들 것인가? ReactDOM.render 담당을 어디에 하면 좋겠느냔 말이다.
App.tsx에다가 하는건 좀 아닌거같다...
(2) React 쇼핑몰 구성을 위한 기초 작업 하기
main.tsx 옆에 index.tsx를 하나 만들자.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
App.tsx는 이렇게 바꿔주자.
import Main from './main'
import GlobalStyles from '@/styles/GlobalStyles';
function App() {
return (
<>
<GlobalStyles />
<Main />
</>
)
}
export default App
src/styles 폴더 안에 GlobalStyles.tsx도 만들어주자.
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react';
const GlobalStyles = () => (
<Global
styles={css`
html, body {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Pretendard', sans-serif; /* 원하는 폰트로 */
background-color: #fff;
color: #111;
}
*, *::before, *::after {
box-sizing: inherit;
}
body {
overflow-x: hidden;
}
`}
/>
);
export default GlobalStyles;
최상위 루트에 index.html이 있다. 뷰포트, 메타태그 등 설정 가능한 곳임.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Shopmall</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
이렇게 수정해주자.
다음으로 해야 할 일은, 이거 정말 중요한건데, 절대경로 alias설정이다.
import를 하거나 어떤 경로를 지정할 때, @ 을 사용해서 절대경로로 쓰면 편하고 이식성도 좋아지고 개발협업시 매우 좋아 필수임.
tsconfig.app.json 을 찾아 열어주고, 거기에 alias설정을 추가하겠음.
"compilerOptions" 항목을 찾아보면 뭐라고 길게 적혀있는데, 맨 아래에다가 다음과 같이 두개 항목을 추가.
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"@/*": ["*"]
}
},
}
이렇게 위의 부분을 넣어주면 됨. "compilerOptions" 내부 baseUrl과 paths를 맞게 넣었나 다시한번 잘 확인할 것.
baseUrl이 "src"인지 확인하고, paths에 "@/*": ["*"] 라고 돼있으면 성공적.
아, 그리고 스크롤 이벤트인 AOS도 세팅하려고 한다.
먼저 AOS를 설치해주자.
npm install aos
AOS의 사용 예시는 다음과 같다.
import React, { useEffect } from 'react';
import AOS from 'aos';
const Main = () => {
useEffect(() => {
AOS.init({
duration: 800, // 애니메이션 시간 (ms)
easing: 'ease-in-out',
once: true, // 스크롤시 한 번만 애니메이션 실행
});
}, []);
return (
<div>
<section data-aos="fade-up">
{/* 신상품 영역 */}
</section>
<section data-aos="fade-left">
{/* 인기상품 영역 */}
</section>
{/* 기타 섹션 */}
</div>
);
};
export default Main;
저렇게 data-aos라는걸 넣으면 되는데, 현재 진행중인 프로젝트에서는 이렇게 안쓰고 있더라.
AOS관련 props만 받아서 쉽게 재사용할 수 있도록 만들어져 있어서 나도 그렇게 해보려고 함.
일단 AOS 기본세팅 (main.tsx에 먼저 넣자.)
useEffect(() => {
AOS.init({
duration: 800,
easing: 'ease-in-out',
once: true,
})
}, [])
/src/ 안에 utils 라는 폴더를 만들고, 그 안에 aos.ts 를 만들었음.
/src/utils/aos.ts
이곳에선 aos 타입 정의 및 props 자동 할당을 위한 헬퍼함수를 만들것임.
// AOS 애니메이션용 공통 props 타입
export type AOSProps = {
'data-aos'?: string
'data-aos-duration'?: number
'data-aos-easing'?: string
'data-aos-once'?: boolean
}
// fade-up 고정값을 기본으로 하는 props 객체
export const aos: AOSProps = {
'data-aos': 'fade-up',
'data-aos-duration': 800,
'data-aos-easing': 'ease-in-out',
'data-aos-once': true,
}
참고로, AOS설치 후 오류가 뜨더라.
"message": "Could not find a declaration file for module 'aos'.
TypeScript가 aos모듈에 대한 타입선언파일 (.d.ts)를 못찾아서 생기는 문제임.
해결방법은 아래와 같음.
npm i --save-dev @types/aos
이렇게 설치해주면 오류는 끝남.
이제 AOS적용도 시켜볼 겸, 빠르게 메인 레이아웃을 잡아보자.
어디까지나 임시이고, 향후 서서히 바꿔나갈 것임.
메인을 잡으면서 키비쥬얼 영역은 바로 동시에 컴포넌트로 분리해뒀는데 소스 보여주겠음.
/** @jsxImportSource @emotion/react */
import { useEffect } from 'react'
import KeyVisual from '@/components/KV/KeyVisual'
import AOS from 'aos'
import 'aos/dist/aos.css'
import * as S from './main.style'
import { aos } from '@/utils/aos'
import { SampleProducts } from '@/data/SampleProducts'
// --- 메인 컴포넌트 ---
const Main = () => {
useEffect(() => {
AOS.init({
offset: 100,
duration: 400,
easing: 'ease-in-out',
once: true,
})
}, [])
return (
<S.MainContainer>
<S.KV>
<KeyVisual />
</S.KV>
<S.QuickMenuWrapper>
<S.QuickMenuButton>신상품</S.QuickMenuButton>
<S.QuickMenuButton>인기상품</S.QuickMenuButton>
<S.QuickMenuButton>특가상품</S.QuickMenuButton>
<S.QuickMenuButton>기획전</S.QuickMenuButton>
</S.QuickMenuWrapper>
<S.BannerArea {...aos}>
프로모션 배너 영역
</S.BannerArea>
<S.ProductSection {...aos}>
<S.SectionTitle>신상품</S.SectionTitle>
<S.ProductList>
{SampleProducts.map((product) => (
<S.ProductCard key={product.id}>
<h3>{product.name}</h3>
<p>{product.price}</p>
</S.ProductCard>
))}
</S.ProductList>
</S.ProductSection>
<S.ProductSection {...aos}>
<S.SectionTitle>인기상품</S.SectionTitle>
<S.ProductList>
{SampleProducts.map((product) => (
<S.ProductCard key={product.id + 100}>
<h3>{product.name}</h3>
<p>{product.price}</p>
</S.ProductCard>
))}
</S.ProductList>
</S.ProductSection>
<S.ProductSection {...aos}>
<S.SectionTitle>특가상품</S.SectionTitle>
<S.ProductList>
{SampleProducts.map((product) => (
<S.ProductCard key={product.id + 200}>
<h3>{product.name}</h3>
<p>{product.price}</p>
</S.ProductCard>
))}
</S.ProductList>
</S.ProductSection>
<S.ProductSection {...aos}>
<S.SectionTitle>기획전</S.SectionTitle>
<S.BannerArea>
{/* 기획전 내용 또는 슬라이더 들어갈 자리 */}
기획전 배너 영역
</S.BannerArea>
</S.ProductSection>
</S.MainContainer>
)
}
export default Main
메인 키비쥬얼을 넣어보자.
영상을 넣을 생각인데, 영상 소스는 그냥 pixabay에서 아무 무료 샘플영상 짤막한거로 받아옴.
/src/components/KV/KeyVisual.tsx
/** @jsxImportSource @emotion/react */
import * as S from './KeyVisual.style'
const KeyVisual = () => {
return (
<S.KVContainer>
<video
autoPlay
muted
loop
playsInline
preload="auto"
>
<source src="/kv/kv_sample_01.mp4" type="video/mp4" />
</video>
<S.OverlayText>메인 KV영상</S.OverlayText>
</S.KVContainer>
)
}
export default KeyVisual
KeyVisual.style.tsx
import styled from '@emotion/styled'
export const KVContainer = styled.div`
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
video {
filter: brightness(0.5);
}
`
export const OverlayText = styled.div`
position: absolute;
z-index: 10;
color: #fff;
font-size: 2.5rem;
font-weight: 700;
text-align: center;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
user-select: none;
opacity: 0.75;
`
혹시 스타일 컴포넌트가 생소한 사람은 이 소스를 보면 이해가 갈 것임.
<div>, <p> 같은 일반적인 태그를 스타일로 감싸는 느낌이라고 해야 하나...? 암튼 이렇게 쓰는것임. 어려울건 없음.
결국, 여차저차 후딱 레이아웃만 잡아본다고 이렇게 간단한 메인 스케치를 완성함.
다음 시간에는 Swiper를 적용시키면서 좀 더 스타일링해 예쁘게 만들어볼 예정이고,
Swiper와 Carousel의 차이를 알아보는 시간을 가져볼까 싶음.
'🎨 Front-end > React.js' 카테고리의 다른 글
React Hook의 이해 | useState, useEffect (0) | 2025.07.18 |
---|