Type something to search...

09 완성 정리

09. 완성 및 오류 정리

최종 파일 목록

1
src/
2
├── main.jsx ✅ 라우터 연결
3
├── router.js ✅ 라우터 설정
4
├── App.jsx ✅ 01단계
5
├── index.css ✅ 각 단계에서 추가
6
└── components/
7
├── SectionTitle.jsx ✅ 05단계
8
├── Nav.jsx ✅ 03단계
9
├── Hero.jsx ✅ 04단계
10
├── AboutMe.jsx ✅ 05단계
11
├── Projects.jsx ✅ 06단계
12
├── Contact.jsx ✅ 07단계
13
└── Footer.jsx ✅ 08단계

전체 index.css 최종본

모든 단계에서 추가한 CSS를 하나로 정리한 버전입니다.

src/index.css
1
/* ─── 초기화 ─────────────────────────────── */
2
@import "reset-css/reset.css";
3
4
body {
5
font-family: "Pretendard Variable", Pretendard, "Malgun Gothic", sans-serif;
6
background-color: #ffffff;
7
color: #1e1e1e;
8
}
9
10
a {
11
text-decoration: none;
12
color: inherit;
13
}
14
15
ul {
16
list-style: none;
17
}
18
19
.container {
20
padding: 0 10vw;
21
margin: 0 auto;
22
}
23
24
/* ─── Nav ───────────────────────────────── */
25
.nav {
26
display: flex;
27
align-items: center;
28
justify-content: space-between;
29
height: 99px;
30
padding: 0 24px;
31
background-color: #ffffff;
32
border-bottom: 1px solid #f0f0f0;
33
}
34
35
.nav-list {
36
display: flex;
37
gap: 8px;
38
}
39
40
.nav-item {
41
display: inline-block;
42
padding: 8px 16px;
43
border-radius: 100px;
44
font-size: 16px;
45
color: #1e1e1e;
46
transition: background-color 0.2s;
47
}
48
49
.nav-item:hover,
50
.nav-item--active {
51
background-color: #f5f5f5;
52
}
53
54
/* ─── Hero ──────────────────────────────── */
55
.hero {
56
height: 524px;
57
display: flex;
58
flex-direction: column;
59
justify-content: center;
60
gap: 40px;
61
padding: 0 24px;
62
}
63
64
.hero-title {
65
font-size: 72px;
66
font-weight: 700;
67
color: #0c0c0d;
68
line-height: 1.2;
69
}
70
71
.hero-subtitle {
72
font-size: 32px;
73
color: #0c0c0d;
74
margin-top: 8px;
75
}
76
77
.hero-buttons {
78
display: flex;
79
gap: 16px;
80
}
81
82
.btn {
83
display: inline-flex;
84
align-items: center;
85
justify-content: center;
86
gap: 8px;
87
padding: 0 20px;
88
height: 40px;
89
border-radius: 6px;
90
font-size: 16px;
91
cursor: pointer;
92
transition: opacity 0.2s;
93
}
94
95
.btn--light { background-color: #e3e3e3; color: #1e1e1e; }
96
.btn--dark { background-color: #2c2c2c; color: #f5f5f5; }
97
.btn:hover { opacity: 0.8; }
98
99
/* ─── 섹션 제목 ──────────────────────────── */
100
.section-title {
101
margin-bottom: 48px;
102
}
103
104
.section-title__row {
105
display: flex;
106
align-items: center;
107
gap: 16px;
108
margin-bottom: 8px;
109
}
110
111
.section-title__line {
112
width: 2px;
113
height: 36px;
114
background-color: #000;
115
}
116
117
.section-title__text {
118
font-size: 48px;
119
font-weight: 700;
120
color: #000;
121
}
122
123
.section-title__sub {
124
font-size: 16px;
125
color: #555555;
126
padding-left: 18px;
127
}
128
129
/* ─── AboutMe ───────────────────────────── */
130
.about {
131
padding: 80px 24px;
132
}
133
134
.about-grid {
135
display: grid;
136
grid-template-columns: 1fr 1fr;
137
gap: 40px;
138
}
139
140
.about-block { margin-bottom: 32px; }
141
142
.about-block__title {
143
font-size: 18px;
144
font-weight: 600;
145
color: #333333;
146
margin-bottom: 16px;
147
padding-bottom: 8px;
148
border-bottom: 1px solid #eeeeee;
149
}
150
151
.about-block__item {
152
display: flex;
153
gap: 16px;
154
padding: 12px 0;
155
font-size: 16px;
156
color: #747474;
157
border-bottom: 1px solid #f5f5f5;
158
}
159
160
.about-block__date {
161
color: #555555;
162
white-space: nowrap;
163
min-width: 100px;
164
}
165
166
.info-row {
167
display: flex;
168
gap: 16px;
169
padding: 8px 0;
170
font-size: 16px;
171
border-bottom: 1px solid #f5f5f5;
172
}
173
174
.info-row__label { width: 70px; color: #3a3a3a; }
175
.info-row__value { color: #727272; }
176
177
/* ─── Projects ──────────────────────────── */
178
.projects {
179
padding: 80px 24px;
180
}
181
182
.project-item {
183
display: grid;
184
grid-template-columns: 1fr 1fr;
185
margin-bottom: 40px;
186
border: 1px solid #f0f0f0;
187
}
188
189
.project-desc {
190
padding: 40px;
191
background-color: #ffffff;
192
}
193
194
.project-number {
195
display: flex;
196
align-items: center;
197
gap: 8px;
198
margin-bottom: 16px;
199
}
200
201
.project-dot {
202
display: inline-block;
203
width: 7px;
204
height: 7px;
205
border-radius: 50%;
206
background-color: #f26440;
207
}
208
209
.project-number__text { font-size: 12px; color: #c7c7c7; }
210
211
.project-title {
212
font-size: 32px;
213
font-weight: 700;
214
color: #000;
215
margin-bottom: 24px;
216
}
217
218
.project-info {
219
display: flex;
220
flex-direction: column;
221
gap: 8px;
222
margin-bottom: 32px;
223
}
224
225
.project-info__row {
226
display: flex;
227
gap: 8px;
228
font-size: 18px;
229
color: #555555;
230
}
231
232
.project-info__label {
233
min-width: 140px;
234
flex-shrink: 0;
235
}
236
237
.project-buttons { display: flex; gap: 16px; }
238
239
.project-btn {
240
display: inline-flex;
241
align-items: center;
242
justify-content: center;
243
width: 160px;
244
height: 52px;
245
background-color: #000;
246
color: #fff;
247
font-size: 18px;
248
transition: background-color 0.2s;
249
}
250
251
.project-btn:hover { background-color: #333; }
252
253
.project-image {
254
background-color: #e8e8e8;
255
min-height: 447px;
256
}
257
258
/* ─── Contact ───────────────────────────── */
259
.contact {
260
padding: 80px 24px;
261
}
262
263
.contact-form {
264
width: 320px;
265
display: flex;
266
flex-direction: column;
267
gap: 20px;
268
}
269
270
.form-field {
271
display: flex;
272
flex-direction: column;
273
gap: 6px;
274
}
275
276
.form-label {
277
font-size: 16px;
278
font-weight: 500;
279
color: #1e1e1e;
280
}
281
282
.form-input,
283
.form-textarea {
284
padding: 0 12px;
285
border: 1px solid #d0d0d0;
286
border-radius: 4px;
287
font-size: 16px;
288
color: #1e1e1e;
289
outline: none;
290
width: 100%;
291
}
292
293
.form-input { height: 40px; }
294
.form-textarea { height: 80px; padding: 10px 12px; resize: none; }
295
296
.form-input::placeholder,
297
.form-textarea::placeholder { color: #b3b3b3; }
298
299
.form-input:focus,
300
.form-textarea:focus { border-color: #2c2c2c; }
301
302
.form-buttons { display: flex; gap: 12px; }
303
304
.form-btn {
305
height: 40px;
306
padding: 0 20px;
307
border: none;
308
border-radius: 4px;
309
font-size: 16px;
310
cursor: pointer;
311
transition: opacity 0.2s;
312
}
313
314
.form-btn--cancel { background-color: #f0f0f0; color: #303030; }
315
.form-btn--submit { flex: 1; background-color: #2c2c2c; color: #f5f5f5; }
316
.form-btn:hover { opacity: 0.8; }
317
318
/* ─── Footer ───────────────────────────── */
319
.footer {
320
padding: 60px 24px;
321
border-top: 1px solid #f0f0f0;
322
}
323
324
.footer-top {
325
display: flex;
326
align-items: center;
327
justify-content: space-between;
328
margin-bottom: 40px;
329
}
330
331
.footer-thanks { font-size: 20px; font-weight: 500; color: #1e1e1e; }
332
333
.footer-sns {
334
display: flex;
335
gap: 16px;
336
align-items: center;
337
}
338
339
.footer-sns__item {
340
display: flex;
341
align-items: center;
342
opacity: 0.7;
343
transition: opacity 0.2s;
344
}
345
346
.footer-sns__item:hover { opacity: 1; }
347
348
.footer-copy { font-size: 14px; color: #1e1e1e; }

자주 발생하는 오류 모음

1. 화면이 안 바뀌어요

1
✅ 체크리스트
2
□ 파일 저장했나요? (Ctrl+S)
3
□ 브라우저에서 새로고침했나요?
4
□ 터미널에 빨간 에러가 있나요?

2. 빨간 에러: Module not found

1
Error: Cannot find module './components/Nav'

원인: 파일 경로나 이름이 틀렸습니다.

1
// App.jsx가 src/ 에 있다면
2
import Nav from './components/Nav' // ✅
3
import Nav from './Components/Nav' // ❌ 대소문자 주의
4
import Nav from '../components/Nav' // ❌ 경로 방향 주의

3. 빨간 에러: is not a function 또는 클릭해도 반응 없음

1
// ❌ 클릭 즉시 실행됨
2
onClick={handleReset()}
3
4
// ✅ 클릭할 때만 실행됨
5
onClick={handleReset}
6
7
// 인자가 필요한 경우
8
onClick={() => handleDelete(item.id)}

4. 경고: Each child in a list should have a unique "key" prop

1
// ❌ key 없음
2
{items.map((item) => <li>{item}</li>)}
3
4
// ✅ key 추가
5
{items.map((item) => <li key={item.id}>{item.name}</li>)}

5. 스타일이 적용 안 돼요

1
// ❌ HTML 방식
2
<div class="nav">
3
4
// ✅ JSX 방식
5
<div className="nav">

핵심 개념 한눈에 보기

개념문법사용 예
컴포넌트const Nav = () => { return <div/> }화면 블록 단위
props<Title text="Hello" />데이터 전달
useStateconst [v, setV] = useState(초기값)변하는 값 관리
조건부 렌더링{조건 ? A : B}상황에 따라 다른 화면
map{list.map(item => <li key={item.id}/>)}목록 반복 출력
이벤트onClick={함수}클릭, 입력 처리
제어 컴포넌트value={state} onChange={핸들러}폼 입력 관리