- ๐ ๊ธฐ๊ฐ: 2023.11.20 ~ 2023.12.01
- โ ์ฃผ์ : Next.js๋ฅผ ํ์ฉํ ์๋ฐ ์์ฝ ์๋น์ค
- ๐ ๋ฐฐํฌ ๋งํฌ
- โ ํ
์คํธ ๊ณ์ :
- [email protected] / asdasd123
- [email protected] / asdasd123
- [email protected] / asdasd123
- [email protected] / asdasd123
- [email protected] / asdasd123
- ์ฐธ์ฌ ์ธ์ ๋ฐ ๋ด๋น ๊ธฐ๋ฅ
- ๊ธฐํ ๋ฐ ๋์์ธ
- OVERVIEW
- ๊ธฐ์ ์คํ
- ํ์ ํ๋ก์ธ์ค
- ํ์๋ณ ๊ตฌํ ๊ธฐ๋ฅ ๋ฐ ํ๊ณ
- ๋ฆฌํฉํ ๋ง ์ดํ ์ถ๊ฐ/๋ณ๊ฒฝ ๋ ๊ธฐ๋ฅ
ํ์ฅ | ๋จ๊ถ์ข ๋ฏผ | ํ์ | ๋ฐ์ฑํ | ํ์ | ์์ง์ | ํ์ | ์ฅ๋ฌธ์ฉ | ํ์ | ์ ์ง์ฃผ |
---|---|---|---|---|
@NamgungJongMin | @HOOOO98 | @jseo9732 | @moonyah | @jinjoo-jung |
|
|
|
|
|
ํ์ฅ | ๊น์งํ | ํ์ | ๊น์ ํ | ํ์ | ๋ฐ์ฐฌ์ |
---|---|---|
@deepredk | @Aleexender | @cyPark95 |
|
|
|
2) ๊ฐ์ธ์ด ๊ฐ๋ฐํ ๋ด์ฉ์ ๋ฐ๋์ pr์ ํตํด์ ํ์๋ค์ ์ฝ๋๋ฆฌ๋ทฐ์ approval์ ๊ฑฐ์น ๋ค ์ ์ฉํ๋ค.
๋จ๊ถ์ข ๋ฏผ
- ์ด๊ธฐ ๊ฐ๋ฐํ๊ฒฝ ์ธํ (์ ๋ ๊ฒฝ๋ก alias ์ค์ / eslint, prettier ์ค์ / ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ / api ์์ฒญ ๋ฉ์๋๋ฅผ ๋ฐํํ๋ ๊ฐ์ฒด ์ค์ )
- ๊ฒ์์์ง์ต์ ํ๋ฅผ ์ํ Metadatas ์์ฑ (robots, sitemap, favicon, title, description)
- ๋ก๊ทธ์ธ/ ํ์๊ฐ์ input ๊ฐ validation
- validation ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ๋์์ธ ๋ณ๊ฒฝ ๋ฐ ๋ฒํผ ํ์ฑํ ์ฌ๋ถ ๊ฒฐ์
- ๊ฐ input ์ปดํฌ๋ํธ ๋จ์ ๋ฆฌ๋ ๋๋ง
- ๋ฐ๋ณต๋๋ react hooks -> custom hooks๋ก ๋ถ๋ฆฌ (useAuthInput, useButtonActivate)
- ๋จ์ ๋น ํ๋ฒ์ ์์ฒญ๋ง์ด ๊ฐ ์ ์๋๋ก debounce๋ฅผ ํจ์์ ์ ์ฉ
๋ก๊ทธ์ธ | ํ์๊ฐ์ |
---|---|
- input ๊ฐ์ ์ฑ์ด ํ ๋ฒํผ ํด๋ฆญ์ ํตํด api ์์ฒญ์ ํ ๋ ๋๋ธํด๋ฆญ์ด๋ ๋จ์๊ฐ์ ์ฌ๋ฌ๋ฒ์ ํด๋ฆญ์ ํ ๊ฒฝ์ฐ ์ฌ๋ฌ๋ฒ์ ์์ฒญ์ด ๊ฐ๋ฅ ์ด์๊ฐ ์์๋ค. debounce๋ฅผ ์ ์ฉํ์ฌ ์๋ํ ๋์์์ ํ๋ฒ์ ์์ฒญ๋ง์ด ๊ฐ๋๋ก ํด๊ฒฐํ๋ค.
const signup = debounce(
async (
email: InputType,
password: InputType,
nickname: InputType,
phone: InputType
) => {
try {
const res = await authRequest.createUser({
email: email.value,
password: password.value,
nickname: nickname.value,
phone: phone.value,
});
console.log(res);
if (res.status === 'SUCCESS') {
router.replace('/auth/signin');
} else {
setSubmitError(res.errorMessage);
}
} catch (error) {
console.log(error);
}
},
200
);
- ๊ฐ input๋ง๋ค value๊ฐ์ ๋ณํ๋ฅผ ์ํ๋ก ์ ์ฅํ๊ณ ๋ ๋๋งํ๋ ์ฝ๋๊ฐ ๋ฐ๋ณต๋์๊ณ validation ๊น์ง ํ๋ ค๊ณ ํ๋ ์ฝ๋๊ฐ ๋๋ฌด ์ง์ ๋ถํด์ง๊ณ ์ ์ง๋ณด์์ฑ์ด ๋จ์ด์ก๋ค. ๊ฐ input ๋ณ ๊ด๋ฆฌ์ validation๊น์ง ํ๋ฒ์ ์ฒ๋ฆฌํ๋ useAuthInput์ด๋ผ๋ custom hook์ผ๋ก ๋ถ๋ฆฌํ์ฌ ํด๊ฒฐํ๋ค.
const useAuthInput = (target: string, password?: InputType) => {
const [input, setInput] = useState({
value: '',
validationPass: false,
});
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>): void => {
if (target === 'email') {
setInput({
value: e.target.value,
validationPass:
/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/.test(
e.target.value
),
});
}
if (target === 'password') {
setInput({
value: e.target.value,
validationPass: /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$/.test(
e.target.value
),
});
}
if (target === 'passwordConfirm') {
if (password) {
setInput({
value: e.target.value,
validationPass: e.target.value === password.value,
});
}
}
if (target === 'name') {
setInput({
value: e.target.value,
validationPass: (input.validationPass =
e.target.value.length >= 2 && e.target.value.length <= 10),
});
}
if (target === 'contact') {
setInput({
value: e.target.value,
validationPass: /^\d{2,3}-\d{3,4}-\d{4}$/.test(e.target.value),
});
}
},
[input, target, password]
);
return [input, handleChange, setInput];
};
- ๊ฐ input๊ฐ ์ ๋ ฅ์ ํด๋น input๋ง์ด ๋ฆฌ๋ ๋๋ง๋๊ฒ ํ๋ ค๊ณ ์ปดํฌ๋ํธ๋ฅผ memo๋ก ๋ฌถ์ด์ฃผ์์ง๋ง ์๋ํ๋๋ก ๊ฐ์ ์ ๋ ฅํ๋ input ๊ฐ๋ง์ด ๋ฆฌ๋ ๋๋ง๋์ง ์์๋ค. ์ปค์คํ ํ ์์ ์์ฑ๋๋ handleChange ํจ์๊ฐ ์ฌ๋ฌ๋ฒ ์์ฑ๋๋ฉฐ ์ ๋๋ก ๋ฉ๋ชจ์ด์ ์ด์ ์ด ๋์ง ์๋๋ค๋ ๊ฒ์ ๊นจ๋ซ๊ณ useCallback์ผ๋ก ์ฌ์ฉํ๋ ์ปค์คํ ํ ์ ํจ์๋ํ ๋ฉ๋ชจ์ด์ ์ด์ ํด์ค์ผ๋ก์จ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์๋ค.
// inputEmail.tsx
const InputEmail = memo(({ email, handleEmail }: EmailProps) => (
<div className='relative my-5'>
<label htmlFor='email' className='text-base leading-10'>
์ด๋ฉ์ผ*
</label>
<input
type='text'
name='email'
id='email'
value={email.value}
placeholder='์ด๋ฉ์ผ์ ์
๋ ฅํด์ฃผ์ธ์.'
onChange={handleEmail}
required
autoComplete='off'
className='border-lightGray top-10 h-14 w-full rounded-[10px] border-2 p-4 text-base text-black'
/>
<ValidationIcon input={email} />
<ErrorMsg target='email' input={email} />
</div>
));
// useAuthInput.ts
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>): void => {
if (target === 'email') {
setInput({
value: e.target.value,
validationPass:
/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/.test(
e.target.value
),
});
}
if (target === 'password') {
setInput({
value: e.target.value,
validationPass: /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$/.test(
e.target.value
),
});
}
if (target === 'passwordConfirm') {
if (password) {
setInput({
value: e.target.value,
validationPass: e.target.value === password.value,
});
}
}
if (target === 'name') {
setInput({
value: e.target.value,
validationPass: (input.validationPass =
e.target.value.length >= 2 && e.target.value.length <= 10),
});
}
if (target === 'contact') {
setInput({
value: e.target.value,
validationPass: /^\d{2,3}-\d{3,4}-\d{4}$/.test(e.target.value),
});
}
},
[input, target, password]
);
-
์๋ฒ ์ปดํฌ๋ํธ์์ ๋ก๊ทธ์ธ์ ๋ฐฑ๋จ์์ set-cookie ํด์ค ๊ฐ์ ์ฝ์ด์ค์ง ๋ชปํ๋ ์ด์ ๋ฐ์. http only ์์ฑ์ด๋ผ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ํด๋น ์ฟ ํค์ ์ ๊ทผํ ์๊ฐ ์์๋ค. ๋ํ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ ๋ฌ๋ฆฌ ์๋ฒ์ฌ์ด๋์์๋ set-cookieํด์ค ์ฟ ํค๊ฐ์ ๊ฐ์ง๊ณ ์์ง ์๊ธฐ ๋๋ฌธ์ ๋ฏธ๋ค์จ์ด๋ฅผ ์ค์ ํ์ฌ ์ฟ ํค๊ฐ์ ๊ฐ๋ก์ฑ๊ฑฐ๋ http only๋ฅผ ํด์ ํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํ๊ฒ ๋์๋ค. set-cookie๋ฅผ ํด์ค๋ค๋ฉด ํ๋ก ํธ์์ ์์ฒญ์ ์ฟ ํค๋ฅผ ์ฌ์ด์ฃผ์ง ์์๋ ์์์ ๋ด์๊ฐ ๊ฒ์ด๋ผ ๊ธฐ๋ํ๋ ๋ฐ์ ๋ฌ๋ผ ์ด ๋ ๋ฐฉ์๋ ์ฌ๋ฐ๋ฅธ ํด๊ฒฐ๋ฐฉ๋ฒ์ด ์๋๋ผ๊ณ ์๊ฐํ๊ณ , ์ค์ ํ์ ์์ next.js app router๋ฅผ ์ธ ๋ ์ด๋ค์์ผ๋ก ์ฟ ํค ์ธ์ฆ์ ํ๋์ง ํผ๋๋ฐฑ ๋ ์ฌ์ญค๋ณด๋ ค๊ณ ํ๋ค.
-
๋ก๊ทธ์ธ์ด ํ์ํ ๋์์์ ๋ก๊ทธ์ธ์ด ๋์ด์์ง ์์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ์ ๋์์ ๋ ํด๋น ํ์ด์ง์์ ๋ก๊ทธ์ธ ํ๋ค๋ฉด ๋ฃจํธ ํ์ด์ง๊ฐ ์๋ ํด๋น ๋์์ ํ๋ ค๋ ํ์ด์ง๋ก ๋์๊ฐ๊ฒ ๊ตฌํํ๊ณ ์ถ์๋ค. ์ด๋ฅผ ์ํด ๋ก๊ทธ์ธ์ด ๋ ๊ฒฝ์ฐ ๋ค๋ก๊ฐ๊ธฐ ๋์์ ํ๋ ๊ฒ์ด ์ด๋จ๊น ์๊ฐํ๊ฒ ๋์์ง๋ง, ์ฒซ ํ์ด์ง๊ฐ ๋ก๊ทธ์ธ์ผ ๊ฒฝ์ฐ์ ํ์๊ฐ์ ์์ ๋ก๊ทธ์ธํ์ด์ง๋ก ์์ ๊ฒฝ์ฐ ๋ฑ ์ฌ๋ฌ ์์ธ ์ฌํญ๋ค์ด ๋ง์ด ๋ฐ์ํ์๋ค. ์ด ๋ถ๋ถ๋ ํผ๋๋ฐฑ์ ๋ฃ๊ณ ๋ฆฌํฉํ ๋ง ๋ ๋ฐ์ํด์ผ๊ฒ ๋ค๊ณ ์๊ฐํ๋ค.
๋ฐฑ์๋์์ ์ฒซ ํ์ ์ด๋ผ ์ค๋ ๊ธฐ๋ ํ๊ณ , ๊ฑฑ์ ๋ ๋ง์๋ ํ๋ก์ ํธ์๋ค. ๋ฐฑ์๋๋ฅผ ์ ๋๋ก ๊ฒฝํํด๋ณธ์ ์ด ์๊ธฐ ๋๋ฌธ์ ๋ด๊ฐ ์๊ตฌํ๋ ์ฌํญ๋ค์ด ๋ฐฑ์๋ ํ์๊ฒ ์ด๋์ ๋์ ์๊ฐ์ด ์ฐ์ด๋์ง ๊ฐ์ด ์กํ์ง ์์๊ณ , ๋ฐฑ์๋ ํ ๋ํ ๋ง์ฐฌ๊ฐ์ง์๋ค. ์๋ชปํ๋ฉด ์๋ก ๊ฐ์ ์ด ์ํ ์๋ ์์ ๊ฒ์ด๋ผ ์๊ฐ์ด ๋ค์๋ค. ์๋ก์ ์ํฉ์ ๋ถ๋ด์์ด ๋งํ๊ณ ์์ ๋กญ๊ฒ ์๊ฒฌ๋ค์ ๊ณต์ ํ๊ธฐ ์ํด์๋ ๋จ์ง ํ ์คํธ๋ก ์์ฌ ์ ๋ฌ์ ํ๋ ๊ฒ์ด ์๋ ์๋ก๊ฐ ์ง์ ๋ํํ ์ ์๋ ์๊ฐ์ด ๋ง์์ผ ํ๋ค๊ณ ๋๊ผ๋ค. ๋ฐ๋ผ์ ์งง์ ๊ฐ๊ฒฉ์ผ๋ก ํ์ ํ์๋ฅผ ํตํด ์๊ฒฌ์ ์กฐ์จํ๊ณ , ํ ์คํธ๋ฅผ ์ํ ์ค์ํ ๋ฏธํ ๋๋ ์คํ๋ผ์ธ ๋ฏธํ ์ ํตํด ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค. ๋๋ถ์ ์ข์ ๋ถ์๊ธฐ๋ก ํ๋ก์ ํธ๋ฅผ ๋๋งบ์ ์ ์์๋ ๊ฒ ๊ฐ๋ค. ์ด๋ฒ ํ๋ก์ ํธ์์ ๊ฐ์ฅ ํฌ๊ฒ ๋๊ผ๋ ๊ฒ์ ๋ด ์ผ์ด ์๋๋๋ผ๋ ์ด๋์ ๋ ๊ณต๊ฐํ ์ ์๋ ์ ๋์ ์ง์์ ๊ฐ์ง๊ณ ์์ด์ผ ๊ฐ๋ฐ์ ๊ธ์ ์ ์ธ ์งํ์ด ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ด์๋ค. ๋ด๊ฐ ํ๋ก ํธ๊ธฐ ๋๋ฌธ์ ํ๋ก ํธ์๋ ๊ธฐ์ ๋ง์ ๊ณต๋ถํ๋ค๋ฉด ์ ๋๋ก ํ์ ํ ์ ์์ ๊ฒ์ด๋ผ ๋๊ผ๊ณ , ๊ฐ๋ฐ ํ๋ก์ธ์ค์ ์์ด์ ์ ์ฒด์ ์ธ ๊ทธ๋ฆผ์ ์์๋๋ ๊ฒ์ด ์์ผ๋ก ํฐ ๋์์ด ๋ ๊ฒ์ด๋ผ ์๊ฐํ๋ค. ์ด๋ฒ ํ๋ก์ ํธ ๋๋ถ์ ํ๋ก ํธ์๋ ๋ฟ๋ง์ด ์๋๋ผ ๋ฐฑ์๋ ํ๋ค์ ์ํฉ๊ณผ ์๋ก์ฌํญ๋ค์ ์ ์ ์์๊ณ , ๋ค์๋ฒ ํ์ ๋๋ ๋์ฑ ์ํ ์ ์๊ฒ ๋ค๋ ์์ ๊ฐ์ ์ป๊ฒ ๋์๋ค.
์ถ๊ฐ๋ก ํ์ฅ์ ๋ถ๋ด๊ฐ์ด ์ฌํ์๋ ํ๋ก์ ํธ์๋ค. ์ต์ํ์ง ์์ ๊ธฐ์ ๋ค๋ก ๊ฐ๋ฐ์ ์งํํ๋ฉด์ ๋ด๊ฐ ๊ณผ์ฐ ํ์๋ค์ ๋ฆฌ๋ฉํ ์ ์์๊น๋ผ๋ ๋๋ ค์๋ ์์๋ค. ๊ทธ๋ฌ๋ ๋ชจ๋ ๋ถ๋ด์ ๋ด๊ฐ ์ง ํ์๋ ์์๋ค. ์ฑํ๋์ ํญ์ ์์ ๊ฐ์ด ๋ถ์กฑํ๋ ๋๋ฅผ ๋ถ๋์์ฃผ์๊ณ , ์ง์๋์ ์ ๋ง ๋ ๋ ํ๊ฒ ๋์ ๋ถ์กฑํ ๋ถ๋ถ๋ค์ ๋ฉ๊ฟ์ฃผ์ จ๋ค. ๋ ์ง์ฃผ๋์ ํ์ ๋ถ์๊ธฐ๋ฅผ ํญ์ ๋ฐ๊ฒ ํด์ฃผ์ จ๊ณ , ๋ฌธ์ฉ๋ ๋ํ ์์ฌํ ๋ด๊ฐ ํ์ ์ ์ ์ํ ์ ๋์ ๋ถ์๊ธฐ๋ฅผ ๋ง๋ค์ด์ฃผ์ จ๋ค. ํ๋ก์ ํธ ๊ฒฐ๊ณผ๋ฟ์ด ์๋๋ผ ์ง์ ํ ๋๋ฃ๋ค์ ์ป์ ์ ์์๋ ๊ฒ ๊ฐ์ ํ์กฑํ ํ๋ก์ ํธ์๋ค.
๋ฐ์ฑํ
- ๋ฒํผ ํ๊ทธ ์ ์ด๋ฏธ์ง ํ๊ทธ vs ๋ฒํผ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๋ฏธ์ง
// ๋ฒํผ ํ๊ทธ ์ ์ด๋ฏธ์ง ํ๊ทธ
<button>
<img/>
</button>
// ์ด ๋ฐฉ๋ฒ์ ๋ฒํผ ์์ ์ด๋ฏธ์ง ํ๊ทธ๋ฅผ ์ง์ ํฌํจ์ํค๋ ๋ฐฉ๋ฒ์
๋๋ค.
// ๋ฒํผ์ ํ
์คํธ ๋๋ ๋ค๋ฅธ ์ฝํ
์ธ ์ ํจ๊ป ์ด๋ฏธ์ง๋ฅผ ํฌํจํ ์ ์์ต๋๋ค.
// ์ด ๋ฐฉ๋ฒ์ ์ ํํ๋ฉด ์ด๋ฏธ์ง์ ์์ฑ์ ์กฐ์ํ ์ ์์ต๋๋ค.
// ๋ฒํผ์ ํ
์คํธ์ ์ด๋ฏธ์ง๋ฅผ ํจ๊ป ํ์ํด์ผ ํ๋ ๊ฒฝ์ฐ ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ด ์ ์ฉํ ์ ์์ต๋๋ค.
// VS
// ๋ฒํผํ๊ทธ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๋ฏธ์ง
<button style={{'backGroundImage:'url(...)'}}/>
// ์ด ๋ฐฉ๋ฒ์ ๋ฒํผ์ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์
๋๋ค.
// ๋ฒํผ ํ
์คํธ๋ ๋ค๋ฅธ ์ฝํ
์ธ ๋ ์ผ๋ฐ์ ์ผ๋ก ๋ฒํผ ๋ด์ ์ถ๊ฐ๋ฉ๋๋ค.
// ์ด ๋ฐฉ๋ฒ์ ์ ํํ๋ฉด ๋ฐฐ๊ฒฝ์ด๋ฏธ์ง์ ์คํ์ผ ์์ฑ์ ์กฐ์ํ ์ ์์ต๋๋ค.
// ๋ฐ๋ฉด์ ๋ฒํผ ์ ์ฒด๊ฐ ์ด๋ฏธ์ง์ฌ์ผ ํ๋ ๊ฒฝ์ฐ ๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ด ๋ ์ ํฉํ ์ ์์ต๋๋ค.
- ์ธํ Placeholder vs label
<input placeholder="์
๋ ฅํด์ฃผ์ธ์"/>
//placeholder ์์ฑ์ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ๋ด์ฉ์ ๋ํ ์์๋ ํํธ๋ฅผ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
// ํ์ง๋ง placeholder๋ ์๊ฐ์ ์ธ ํํธ๋ก๋ง ์ ๊ณต๋๊ธฐ ๋๋ฌธ์
// ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์ ๋ฑ์๊ฒ๋ ์ถฉ๋ถํ ์ ๋ณด๋ฅผ ์ ๊ณตํ์ง ๋ชปํ ์ ์์ต๋๋ค.
// VS
<label for="test">ํ
์คํธ ์ธํ</label>
<input/>
// ์์ ์์์์ for ์์ฑ์ input ์์์ id ๊ฐ๊ณผ ์ผ์น์์ผ ์ด๋ค ์
๋ ฅ ํ๋์ ๊ด๋ จ์ด ์๋์ง ์ง์ ํฉ๋๋ค.
// ์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์ ๋ฐ ์๊ฐ์ ๋์์ธ๊ณผ ์๊ด์์ด ๋ช
ํํ ์ค๋ช
์ ์ ๊ณตํ ์ ์์ต๋๋ค.
๋น๋ก๊ทธ์ธ ์ ์์ฝํ๊ธฐ | ๋ก๊ทธ์ธ ์ ์์ฝํ๊ธฐ |
---|---|
๋น๋ก๊ทธ์ธ ์ ์ฅ๋ฐ๊ตฌ๋ | ๋ก๊ทธ์ธ ์ ์ฅ๋ฐ๊ตฌ๋ |
---|---|
๊ฐ์ค ์์ฝ ์ ํจ์ฑ ๊ฒ์ฌ | |
---|---|
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ CSS override
rsuite? vercel์ฌ์์ ์ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์ ๋ฆฌ์กํธ์ ๋ฅ์คํธ์ ์ต์ ํ๋์ด ์์ต๋๋ค.
์ํฉ : ์๋ฐ ๋ ์ง ์ ํ์ ์ํด DaterangePicker๋ฅผ ๊ฐ์ ธ์ ์ฌ์ฉํ์ต๋๋ค.
๋ฌธ์ : ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๋, ๊ณต์๋ฌธ์๋ฅผ ์ ๋ ํ์ง ์์ ๋ฐ์ํ์ต๋๋ค.
CSS ๋ชจ๋์ด ๊ฐ์ด ์ค์น๊ฐ ๋์ด ์ฌ์ฉํ์ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธํ๋ฉด CSS๊ฐ override๋์ด ๋ค๋ฅธ ํ์ด์ง๋ ๋ ์ด์์์ด ๊นจ์ง๋ ํ์์ด ์ผ์ด๋ฌ์ต๋๋ค.
ํด๊ฒฐ : ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ด์ ์ด๋ฏธ ํด๊ฒฐ๋ฐฉ์์ด ๋์ ์์์ต๋๋ค.
๊ธฐ์กด์ ์ฌ์ฉํ๋'rsuite/dist/rsuite.min.css';
๋์'rsuite/dist/rsuite-no-reset.min.css';
๋ฅผ ์ฌ์ฉํ๋ฉด ๋์์ต๋๋ค.
๋๋์ : ์ฌ์ค ์ด ๋ฌธ์ ๋ ์ง์ ํด๊ฒฐํ ๋ฌธ์ ๊ฐ ์๋๋ผ ๋ฆฌํฉํ ๋ง ์ดํ๋ก ๋ฏธ๋ฃฌ ํ์ ์กฐ์ ๋ถ์ด ์ฐพ์์ฃผ์ ํด๊ฒฐ์ฑ ์ด์์ต๋๋ค. ์์ผ๋ก๋ ์ด๋ค ๊ฒ์ด๋ ๊ณต์๋ฌธ์๋ฅผ ๊ผผ๊ผผํ ๋ณด๊ณ ์ฌ์ฉํด์ผ๊ฒ ์ต๋๋ค.
์ด๋ฒ ํ๋ก์ ํธ์์๋ ๊ฒ์ ์์ง ์ต์ ํ(SEO), ์คํฌ๋ฆฐ ๋ฆฌ๋๋ฅผ ์ฌ์ฉํ๋ ์ ์ ๋ค์ ์น ์ ๊ทผ์ฑ์ ๊ณ ๋ คํด๋ณด๊ธฐ ์ํด NEXT๋ฅผ ์ฌ์ฉํ์๋ ํ ์๊ฒฌ์ ๋์ ํ๋ค. ๋ค๋ง ๋ฌธ์ ๋ NEXT์ ๋ํ ์ดํด๋๊ฐ ๋จ์ด์ง ์ํ๋ก ๊ฐ๋ฐ์ ์์ํ๋ค๋ ์ ์ด๋ค. ๋จ์ํ ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ ์ฐจ์ด์ ๋ํด์๋ง ์๊ณ ์์๋๋ฐ, ์ค์ ํ๋ก์ ํธ์์๋ ๊น์ด๊ฐ ๋ ๊น์ด์ง๊ณ , ์ํธ์์ฉ์ด ๋ง์์ง๊ธฐ ๋๋ฌธ์ ์์ ์ง์๋ง์ผ๋ก ๊ฐ๋ฐ์ ์งํํ๊ธฐ๊ฐ ์ด๋ ค์ ์ต๋๋ค. ์ค๊ฐ์ค๊ฐ ํ์ํ ๋ด์ฉ์ ๊ณต์๋ฌธ์, ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ๋ฉฐ ๊ณต๋ถ๋ฅผ ํ์ต๋๋ค. ์์ผ๋ก๋ ์๋ก์ด ํ๊ฒฝ์ ์ง์์ ์ผ๋ก ๋ ธ์ถ ์์ผ ์ด๋ฐ ์ฑ์ฅ์ ์ด๋ฃจ์ด ๋๊ฐ์ผ ๊ฒ ๋ค๊ณ ์๊ฐํ์ต๋๋ค. ์ฒ์ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋ถ๋ค๊ณผ ํ์ ์ ํตํด ๋๋ ์ ์ ๋ฐ์ดํฐ ๊ตฌ์กฐ, API ๋ช ์ธ ๋ถ๋ถ์์ ํ์คํ๊ฒ ๋ฌธ์ํ๋ฅผ ํ๊ณ ์ง์์ ์ผ๋ก ์ํต์ ํ์ฌ ์ค์ฐจ๊ฐ ์๋๋ก ํด์ผ ํ๋ค๋ ๊ฒ์ ๋๊ผ์ต๋๋ค.
์์ง์
- ๊ฐ ํ์ด์ง์ ๋ง๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์ปดํฌ๋ํธํ
- ์ฅ๋ฐ๊ตฌ๋ ์กฐํ
- ์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ธด ์ํ ๋ฐ์ดํฐ (์ด๋ฏธ์ง, ์ํ๋ช , ์ต์ ๋ฑ)์ ๋ฐ๋ฅธ ์ํ๋ณ ๊ตฌ๋งค ๊ธ์ก, ์ ์ฒด ์ฃผ๋ฌธ ํฉ๊ณ ๊ธ์ก ๋ฑ์ ํ๋ฉด์ ์ถ๋ ฅ
- ์ง๋ ์ฒดํฌ์ธ ๋ ์ง, ์ฌ๊ณ ์์์ผ๋ก ์ธํ ์์ฝ ๋ง๊ฐ ์ํ ํ์
- ์ฅ๋ฐ๊ตฌ๋ ๊ฐ๋ณ ์ญ์ ๊ธฐ๋ฅ ๊ตฌํ
- ์ฅ๋ฐ๊ตฌ๋ ์ฒดํฌ ๋ฐ์ค๋ฅผ ํตํด ์ญ์ ๊ธฐ๋ฅ ๊ตฌํ
- ์์ฝ ๋ถ๊ฐ ์ํ ์ญ์ ๊ธฐ๋ฅ ๊ตฌํ
- ์์ฝ ๋ง๊ฐ ์ํ์ ์ ์ธํ ์ ์ฒด ์ ํ / ํด์ ๊ธฐ๋ฅ ๊ตฌํ
- ์ฒดํฌ ๋ฐ์ค๋ฅผ ํตํด ๊ฒฐ์ ํ ์ํ์ ์ ํ/์ ์ธ ๊ธฐ๋ฅ ๊ตฌํ
- ์ฅ๋ฐ๊ตฌ๋์์ ์ฃผ๋ฌธํ๊ธฐ ๋ฒํผ ํด๋ฆญ ์, ์์ฝ(์ฃผ๋ฌธ) ํ์ด์ง๋ก ์ด๋
ํค๋ | ์ฅ๋ฐ๊ตฌ๋ ๊ฐ๋ณ, ์ ํ ์ญ์ |
---|---|
์์ฝ ๋ถ๊ฐ ์ฅ๋ฐ๊ตฌ๋ ์ญ์ | ์ ์ฒด ์ ํ, ์ ํ ํญ๋ชฉ ์์ฝ |
---|---|
- ํ์ํ ์์น์์๋ง ํธํฐ ํ์ NextJS ์๋ฒ์์ ํธํฐ๊ฐ ํ์ํ ํ์ด์ง์ธ์ง ๊ตฌ๋ถํ ๋ค์ ๋ ๋๋ง์ด ๋๊ธฐ ์ ์ ํธํฐ ์ ๋ฌด๋ฅผ ํ๋จํ์ฌ ๋ณด์ฌ ์ฃผ๊ณ ์ถ์๋๋ฐ ์๋ฒ ์ปดํฌ๋ํธ์ header, cookie (from next/header)๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ณด๋ฅผ ๋ฐ์์๋ ํ์ด์ง๋ฅผ ํ๋จํ ์ ์๋ ์ํ๋ ๊ฐ์ ์ฐพ์ ์ ์์๋ค. ํ๋ก์ ํธ ๊ธฐํ ๋๋ฌธ์ ํ์ํ ํ์ด์ง๋ง๋ค ํธํฐ๋ฅผ ๋ฃ์ด์ฃผ๋ ๋ฐฉ์์ผ๋ก ์์ ํด๊ฒฐํ์ง๋ง ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ ์ฐจ์ด์ ๋ํด์ ๊ณต๋ถํ ์ ์์๋ค. ์ดํ ๋ฆฌํํ ๋ง ๊ณผ์ ์์ ์์ฌ์ ๋ ๋ถ๋ถ์ ๊ฐ์ ํด๋ณด๋ ค๊ณ ํ๋ค.
- ์ฅ๋ฐ๊ตฌ๋ ์ ํ
์ฅ๋ฐ๊ตฌ๋์ ์์ฝ ๋ถ๊ฐ(์ฒดํฌ์ธ ๋ ์ง๊ฐ ์ง๋ฌ๊ฑฐ๋ ์์ฝ ๊ฐ๋ฅํ ๋ฐฉ์ ์๊ฐ ์๋ ๊ฒฝ์ฐ) ํญ๋ชฉ์ ์ฒดํฌ๊ฐ ๋ถ๊ฐ๋ฅํ๊ฒ ์ฒ๋ฆฌ, ์ ์ฒด ์ ํ, ํ์ํ ํญ๋ชฉ๋ง ์ ํ ํ ์ญ์ , ๊ฐ๋ณ ์ญ์ ๋ฑ ๊ณ ๋ คํด์ผํ ๊ฒฝ์ฐ์ ์๊ฐ ๋ง์ ๋ง์ ์ด๋ ค์์ด ์์๋ค.
- Strict ๋ชจ๋๋ก ์ธํ ์ ์ฒด ์ ํ ๋ฐฐ์ด์ ๊ฐ์ ์์ดํ ์ด ๋ค์ด๊ฐ ์ค์ ์ ํํ ์์ 2๋ฐฐ๊ฐ ์ ํ ์ฒ๋ฆฌ๋๋ ์ด์
- ์ฒซ ๋ ๋๋ง ์ ์ ์ฒด ์ ํ์ด ๋ ๋ ๊ฐ checkbox์ onChange๊ฐ ๊ฐ๋ณ์ ์ผ๋ก ์ธ์๋์ง ์์ ๊ฐ ํญ๋ชฉ์ด ์ฒดํฌ๊ฐ ๋์์ ๋ ๊ทธ์ ๋ฐ๋ฅธ ๋ฐฐ์ด ๊ฐ์ ๋ฐ๊ฟ์ค์ผํ๋ ์ด์
- ์ด ์ธ์๋ ๋ง์ ์ด์๊ฐ ์์์ง๋ง
useEffect
์useState
๋ฅผ ์ ๊ณ ๋ คํ์ฌ ํด๊ฒฐํ๋ฉด์ ๋ค์ ๋ฆฌ์กํธ์ ๋ผ์ดํ ์ฌ์ดํด์ ๊ณต๋ถํ ์ ์์๋ค.
์ด์ ํ ์ด2 ํ๋ก์ ํธ์์ ์ต์ํ๋ ํ์ด์ง ๋ผ์ฐํฐ๋ฅผ ์ฌ์ฉํ์๋๋ฐ ์ด๋ฒ ํ๋ก์ ํธ์์ app ๋ผ์ฐํฐ๋ฅผ ์ฌ์ฉํ๋ฉด์ app ๋ผ์ฐํฐ ๊ฐ๋ฐ ๊ฒฝํ์ ํ ์ ์์๊ณ ์ด์ ์๋ ๊ณ ๋ฏผํ์ง ์์๋ ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ๋ํด์ ๊ณต๋ถํ ์ ์์์ต๋๋ค.
๋ฐฑ์๋์์ ํ์
์ ํตํด์ ๋ง์ ๊ฐ๋ฐ์ด ์งํ๋๊ธฐ ์ ์ ๋น ๋ฅด๊ฒ ๋ฐ์ดํฐ ํ์์ด๋ api ๋ฌธ์๋ฅผ ํต์ผํ ๋ค์ ์์
ํด์ผ์ง ํฐ ๋ฌธ์ ๋ฐ์ํ์ง ์๊ณ ๋ฌธ์ ํด๊ฒฐ๋ ์์ํ๊ฒ ํ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋์๊ณ ๋ฌธ์ํ์ ์ํต์ ์ค์์ฑ์ ์๊ฒ ๋์์ต๋๋ค.
์ฅ๋ฐ๊ตฌ๋ ๊ธฐ๋ฅ ๊ตฌํ์ ๋ด๋นํ๋ฉด์ ๋ํ
์ผํ ์์
๋ค์ด ๋ง์์ ์ํ๊ด๋ฆฌ๋ ๋ผ์ดํ ์ฌ์ดํด์ ๊ณต๋ถํ ์ ์๋ ์ข์ ๊ฒฝํ์ด ๋์์ต๋๋ค. ์ฝ๋์ ๊ฐ๋
์ฑ์ ์ํด์ ์ปดํฌ๋ํธ์ ๋ถ๋ฆฌ ๋ฐ ์ปจ๋ฒค์
์ ๋ฐ๋ฅด๋ ค๊ณ ๋
ธ๋ ฅํ์ต๋๋ค. ํ์๋ค๊ณผ ๋๋ฉด์ผ๋ก ์ํตํ์ฌ ์ํ ํ๊ฒ ํ๋ก์ ํธ๋ฅผ ๋ง๋ฌด๋ฆฌ ํ ์ ์์์ต๋๋ค!
์ฅ๋ฌธ์ฉ
- main Carousel : autoplay ์ ์ฉ
- main Icon : ์๋ฐ ์ ์ ์นดํ ๊ณ ๋ฆฌ ๋ณ ๋ถ๋ฅ ์์ด์ฝ, ๋ถ๋ฅ ํ์ด์ง์ ์ฐ๊ฒฐ๋์ด ์๋ค.
- main contents 01 โ ์ง์ญ ๋ณ ํ์ ๋ณด์ฌ์ฃผ๊ธฐ, API ์ฐ๊ฒฐ
- main contents 02 โ ์ง์ญ ๋ณ ํธํ ๋ณด์ฌ์ฃผ๊ธฐ, API ์ฐ๊ฒฐ
- main contents 03 โ ์ง์ญ ๋ณ ์ ์ฒด ์์ ๋ณด์ฌ์ฃผ๊ธฐ (๋ถ๋ฅ ํ์ด์ง์ ์ฐ๊ฒฐ)
- ์นดํ ๊ณ ๋ฆฌ ๋ณ ๋ถ๋ฅ ๋๋กญ๋ค์ด
- ์ง์ญ ๋ณ ๋ถ๋ฅ ๋๋กญ๋ค์ด
- ์์ ์นด๋ ์ ์, infinite scroll ์ ์ฉ
- ์๋ฐ ์ ์ ๋ชฉ๋ก ์กฐํ API ์ฐ๊ฒฐ
- ์์๋ค์ ๊ฐ๊ฐ์ detail page์ ์ฐ๊ฒฐ
๋ฉ์ธํ์ด์ง carousel | ๋ฉ์ธํ์ด์ง contents |
---|---|
์นดํ ๊ณ ๋ฆฌ ํ์ด์ง | ์์ธํ์ด์ง๋ก ์ด๋ |
---|---|
ํธํ ์นดํ ๊ณ ๋ฆฌ์ ์ง์ญ์ ๋์์ ๋ถ๋ฅํด์ผ ํ๋ ์ํฉ์์, URL์ ํ์ฉํ์ฌ ํ์ด์ง๋ฅผ ๊ตฌ์ฑํ๋ ๊ณผ์ ์์ ๋ฐ์ํ ์ด๋ ค์์ด ์์๋ค.
- ์ํฉ : ์ฒ์์๋ URL์ slug๋ก ์ค์ ํ์ฌ ํธํ ์ ๋ถ๋ฅํ๋ ค๊ณ ํ์ผ๋, ์ด ๋ฐฉ์์ด ๋๋ฌด ํท๊ฐ๋ ค์ ๋ก์ง์ ๋ณ๊ฒฝํ๊ฒ ๋์๋ค.
- ๋ฌธ์ : slug๋ฅผ ์ฌ์ฉํ URL์ category์ location์ ๋ช ์์ฑ์ด ๋ถ์กฑํด ํผ๋์ ์ด๋ํด์ ์ฌ์ฉ์๊ฐ ์ํ๋ ์ ๋ณด๋ฅผ ์ ํํ ์๋ณํ๊ธฐ ์ด๋ ต๋ค.
- ํด๊ฒฐ : URL์
product?category=&location=
๋ก ๋ช ์์ ์ผ๋ก ๋ณ๊ฒฝํ์ฌ ๊ฐ๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ช ํํ ๋ํ๋ด๊ฒ ๋์๋ค. ์ฌ์ฉ์๊ฐ ์ฝ๊ฒ ํํฐ๋งํ๊ณ ์ํ๋ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์๋๋ก ๊ฐ์ ์ด ๋์๋ค. - ๋๋ ์ : URL ๊ตฌ์กฐ์ ์ค์์ฑ์ ๊นจ๋ฌ์๊ณ , ๋ช ํํ ๋งค๊ฐ๋ณ์๋ฅผ ํตํด ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๊ฒฐ์ ์ ๋ด๋ ธ๋ค.
์ด ํ๋ก์ ํธ๋ฅผ ํตํด ๋ฐฑ์๋์ ์ํตํ๋ฉด์ api ์ฐ๊ฒฐ๊ณผ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ๋ ํ์ ๊ฒฝํ์ ํ์์ต๋๋ค. ํ๋ก ํธ ๊ฐ๋ฐ์์ ๋ฅ์คํธ์ ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ๋ฉด์ ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํค๊ณ ์ ํ์์ต๋๋ค. ๋ด๋นํ๋ ๋ถ๋ถ์์๋ ๋ฉ์ธ ํ์ด์ง์ ๋ถ๋ฅ ํ์ด์ง ๊ฐ์ ์ฐ๊ฒฐ ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๊ธฐ ์ํด ์ ๊ฒฝ์ ์ผ๋ ๊ฒ ๊ฐ์ต๋๋ค. ํ์๋ค ๊ฐ์ ์ ๊ทน์ ์ด๊ณ ํ๋ฐํ ์ํต์ผ๋ก ์ธํด ์งง์ ๊ธฐ๊ฐ์ด์ง๋ง ๋ฌด์ฌํ ํ๋ก์ ํธ๋ฅผ ๋ง๋ฌด๋ฆฌ ์ง์ ์ ์์์ต๋๋ค!โค๏ธ
์ ์ง์ฃผ
- ์ฃผ๋ฌธ ๊ฒฐ์ ํ์ด์ง , api ์ฐ๊ฒฐ
- ์ฃผ๋ฌธํ ์์ ์ ๋ณด ๊ฒฐ์ ํ์ด์ง๋ก ๊ฐ์ ธ์ค๊ธฐ
- ์ด์ฉ์ , ์์ฝ์ ์ ๋ณด ๋์ผํ๋ฉด ์์ฝ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
- ์ด์ฉ์ ์ ๋ณด, ๊ฒฐ์ ๋ฐฉ์, ํ์ ์ฒดํฌ๋ฐ์ค ์ ํ ํ ๊ฒฐ์ ๊ฐ๋ฅ
- ์ฃผ๋ฌธ ๋ด์ญ ์์ธ ํ์ด์ง , api ์ฐ๊ฒฐ
- ๊ฒฐ์ ์๋ฃ โ ์ฃผ๋ฌธ ์๋ฃ ์์ธ ํ์ด์ง
- ๊ฒฐ์ ๊ธ์ก, ๊ฒฐ์ ์๋จ, ์ด์ฉ์, ์์ฝ์ ์ ๋ณด ๋ณด์ฌ์ฃผ๊ธฐ
- ์ฃผ๋ฌธ ๋ด์ญ ๋ชฉ๋ก ํ์ด์ง, api ์ฐ๊ฒฐ
- ์ฌ์ฉ์๊ฐ ์์ ๊ฒฐ์ ํ ๋ ์ง ๊ธฐ์ค์ผ๋ก ์์ ๋ชฉ๋ก ๋์ด์ฃผ๊ธฐ
- ์์ธ๋ณด๊ธฐ ํด๋ฆญ์ ์์ธ ํ์ด์ง๋ก ์ด๋
์์ ์์ฝ ์ ๋ณด ์กฐํ & ๊ฒฐ์ ์๋ฃ | ๊ฒฐ์ (์์ฝ)ํ๋ ์์ ๋ชฉ๋ก ์กฐํ |
---|---|
์ฅ๋ฐ๊ตฌ๋ ๋ด์ ์์ 2๊ฐ ๊ฒฐ์ | |
---|---|
-
Next.js SSR์ ์ฌ์ฉํ SEO๋ฅผ ์ํ Next.js๋ฅผ ์ฌ์ฉํ ํ๋ก์ ํธ๊ฐ ์ด๋ฒ์ด ์ฒ์์ด๋ผ์ ๊ทธ๋ฐ์ง, ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ ๊ฐ์ ๋ ๋๋ง์ ์ฐจ์ด์ ์ด๋ props์ ๋ฌํ๋ ๋ฐฉ์๋ค์ ํค๋งธ๋ ๊ฒ ๊ฐ์ต๋๋ค. ๊ทธ๋ฌ๋ค ๋ณด๋ ์๋ฒ ์ปดํฌ๋ํธ์์ useState, useEffect๋ฅผ ์ฌ์ฉํ๊ฒ ๋๊ณ ๊ฒฐ๊ณผ โuse clientโ๋ฅผ ์์ฑํ๋ผ๋ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๋ง์ฃผํ๋ฉด์ ๋ค์ ์๋ฒ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ์ ์ฐจ์ด์ ์ ์ ๋๋ก ๊ณต๋ถํ๊ณ ์ฝ๋๋ฅผ ์์ฑํด์ผ๊ฒ ๋ค๊ณ ์๊ฐํ์ต๋๋ค. ์ดํ lifecycle hooks๊ฐ์ ์ํธ์์ฉ์ฑ์ ํฌํจํ๋ ์ปดํฌ๋ํธ๋ผ๋ฉด ๊ทธ๊ฒ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ก ๋ง๋ค๊ณ , ๊ทธ๋ ์ง ์์ผ๋ฉด ์๋ฒ์ปดํฌ๋ํธ๋ก ๊ด๋ฆฌ๋ฅผ ํ๋ ๋ฐฉ์์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ next.js๋ฅผ ์ ์ฌ์ฉํ๋์ง, ์ด๋ค ๋ถ๋ถ์์ ์ฌ์ฉํด์ผํ๋์ง ๋ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด๋๊ฐ๋ฉฐ ๋ฐฐ์ธ ์ ์์์ต๋๋ค.
์ด ๋ถ๋ถ์ ์์ ํ ํด๊ฒฐํ์ง ์์ ์ํ์ด์ง๋ง ๊ฒช์ ๋ฌธ์ ์ด๊ธฐ์ ์ ์์ต๋๋ค. ์ง์ญํ๋ฉด Chunk ํ์ผ์ ๋ถ๋ฌ์ค์ง ๋ชปํด์ ๋ฐ์ํ๋ ์๋ฌ๋ผ๊ณ ํฉ๋๋ค. ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ์์ ์บ์ฑ์ด ๋์๊ฑฐ๋, ์ด์ ๋ฒ์ ์ ํ์ด์ง๊ฐ ๊ณ์ ์ด๋ ค์๋๊ฐ ํ๋ ๋ฑ์ ์ด๋ฅ๋ก ์ธํด ์ง๊ธ์ ์กด์ฌํ์ง ์๋ ์ด์ ์ ๋ฒ์ chunk ํ์ผ์ ์์ฒญํ๊ฒ ๋๋ฉด์ ChunkError๊ฐ ๋ฐ์ํ๋ ํ์์ธ๋ฐ, ์ด ์๋ฌ๊ฐ ๊ณ์ ๋จ๋ฉด์ ํ๋ฉด์ด ๋ณด์ด์ง ์๋ ๊ฒ์ด ์๋๋ผ ์ด ๋ฒ ์ค ํ๋ฒ๊ผด๋ก ๋ฐ์ํ๊ณ ์๋ก๊ณ ์นจํ๋ฉด ๋ธ๋ผ์ฐ์ ํ๋ฉด์ ์ ์๋์ ํด์ ์ ํํ ์๋ฌ ๋ฐ์ ์ด์ ๋ ์ฐพ์ง ๋ชป ํ์ต๋๋ค. ์ด๋ค ์ด์ ๋ก ์๋ฌ๊ฐ ๋ฐ์ํ๋์ง ๋์ถฉ ์ดํด๋ ํ์ง๋ง ์ ํํ๊ฒ ํด๊ฒฐ์ ํ ๊ฒ์ ์๋๋ผ์ ๋ ์ฐพ์๋ณด๊ณ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด ๋๊ฐ ์์ ์ ๋๋ค.
์ด๋ฒ ํ๋ก์ ํธ๋ ์๋ฐ ์์ฝ ์๋น์ค๋ก, ์ ๊ฐ ๋งก์๋ ๋ถ๋ถ์ธ ์์ ๊ฒฐ์ ํ์ด์ง์ ์ฃผ๋ฌธ ๋ด์ญ ๋ชฉ๋ก ํ์ด์ง๊ฐ์ ์ํธ์์ฉ์ ์ํํ๊ฒ ํ๊ธฐ์ํ ์ปดํฌ๋ํธ ๊ตฌ์กฐ๋ฅผ ๋๋๊ณ ์ฝ๋๋ฅผ ์์ฑํ๋๋ก ๋ ธ๋ ฅํ์์ต๋๋ค. next.js ํ๋ ์์ํฌ๋ฅผ ํ๋ก์ ํธ์ ์ฒ์์ผ๋ก ์ฌ์ฉํ๋ฉด์ ๊ธฐ์กด๊ณผ๋ ๋ฌ๋ฆฌ ์๋ฒ์ฌ์ด๋๋ ๋๋ง์ ํตํด ์ฝ๋๋ฅผ ์ง๊ณ ๊ตฌ์กฐ๊ฐ ๋ฌ๋ผ์ง ๋ถ๋ถ์ด ์ด๋ ต๋ค๊ณ ๋๊ปด์ก์ง๋ง ์ข์ ํ์๋ถ๋ค์ ๋ง๋์ next.js์ ๋ํด์ ๋ ๋ง์ด ๋ฐฐ์ธ ์ ์์๊ณ , ๋ฐฑ์๋๋ถ๋ค๊ณผ ํ์ ํ๋ฉด์ API ๋ฌธ์๋ฅผ ๋ณด๊ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ก๊ณ , ๋ฐ์ดํฐ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ๊น์ง ๊ฐ์ด ์ํตํ๊ณ ์์ ํ๋ ๊ณผ์ ์ ๊ฑฐ์น๋ฉด์ ํ์ ์ ์ค์์ฑ๋ ๋ค์ ํ ๋ฒ ๋๋ ์ ์์๋ ํ๋ก์ ํธ์์ต๋๋ค.
๋จ๊ถ์ข ๋ฏผ
๋ง์ดํ์ด์ง์ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ๊ณผ ํ์ ํํด ์์ฒญ์ ํ๋ ๋ฒํผ๋ค์ ์ถ๊ฐํ์๋ค. ํด๋น ๋ฒํผ๋ค์ ๋๋ฅด๋ฉด ์์ฒญ ์ฌ๋ถ๋ฅผ ๋ค์ ํ๋ฒ ํ์ธํ๋ ๋ชจ๋ฌ์ด ๋จ๊ฒ ๋๊ณ ํ์ input ๊ฐ์ ์ฑ์ฐ๊ณ validation์ด ์ถฉ์กฑ๋๋ฉด ํ์ธ ๋ฒํผ์ด ํ์ฑํ๋์ด ์์ฒญ๋ค์ ๋ณด๋ผ ์ ์๊ฒ ๋๋ค.
๋ง์ดํ์ด์ง ๋ฒํผ ์ถ๊ฐ | ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ๋ชจ๋ฌ | ํ์ ํํด ๋ชจ๋ฌ |
---|---|---|
2. ๋ฏธ๋ค์จ์ด๋ฅผ ์์ฑํ์ฌ ํ์ด์ง ๋ ๋๋ง ์ด์ ์๋ฒ ์ธก์์ ๋ก๊ทธ์ธ ์ฌ๋ถ๋ฅผ ํ๋ณ
๋ก๊ทธ์ธ ์ฌ๋ถ์ ๋ฐ๋ผ ํ์ด์ง๋ฅผ ๋ณด์ฌ์ค์ง ๋ฆฌ๋ค์ด๋ ํธ ์ํฌ์ง์ ๋ํ ์ฌ๋ถ๋ฅผ ํ๋ณํ๋ ๊ธฐ์กด ๋ก์ง์ ํด๋น ํ์ด์ง์์ useEffect๋ฅผ ํตํด ๋ก๊ทธ์ธ ์ฌ๋ถ๋ฅผ ํ๋ณํ๋ api์์ฒญ์ ๋ณด๋ด๊ณ ๊ทธ ์ฌ๋ถ์ ๋ฐ๋ผ ํ์ด์ง๋ฅผ ๋ฆฌ๋ค์ด๋ ํธ ์ํค๋ ๋ฐฉ์์ด์๋ค. ์ด ๋ฐฉ๋ฒ์ ๋ฌธ์ ๋ ์ธ๊ฐ์ฌ๋ถ๋ฅผ ํ๋ณํ๋ ์ปดํฌ๋ํธ๋ฅผ ์๋ฒ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ ์ ์๋ค๋ ์ ๊ณผ ํ์ด์ง๊ฐ mount๋์ด ๋ ๋๋ง ํ ๋ api์์ฒญ์ด ๊ฐ๊ธฐ ๋๋ฌธ์ ๋ฆฌ๋ค์ด๋ ํธ ์ ์๋ ์ ๊น ํ์ด์ง๊ฐ ๋ณด์ด๋ฉฐ ๊น๋นก๊ฑฐ๋ฆฌ๋ ํ์์ด ์ผ์ด๋๋ค๋ ์ ์ด์๋ค. ๋ฐ๋ผ์ ํด๋น ํ์ด์ง์ ๋ ๋๋ง ์ด์ ์ ์ธ์ฆ์ฌ๋ถ๋ฅผ ํ๋ณํ๊ธฐ ์ํด ๋ฏธ๋ค์จ์ด๋ฅผ ์์ฑํ๋ค.
์ด ๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์์ฑํ๋ฉด์ ์ฟ ํค ์ด์๊ฐ ์๊ฒผ๋๋ฐ, ๋ฐฑ์๋ ๋จ์์ Set-cookie๋ฅผ ํด์ฃผ๋๋ผ๋ next์๋ฒ์๋ ์ฟ ํค๋ผ๋ ๊ฐ๋ ์ด ์๊ธฐ ๋๋ฌธ์ next์๋ฒ์์์ ์์ฒญ์์ ์ฟ ํค๊ฐ ๋ด๊ฒจ๊ฐ์ง ์์ ์ธ์ฆ ์์ฒญ์ ํ ์๊ฐ ์์๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ก๊ทธ์ธ์ ๋ธ๋ผ์ฐ์ ์ Set-cookie๋ ํ ํฐ๋ค์ ์ง์ next์๋ฒ์ ๋ณ์๋ก ๋ถ๋ฌ์ header์ cookie๋ก ์ง์ ๋ฃ์ด์ api ์์ฒญ์ ํด์ฃผ์ด์ผ ํ๋ค. ์ด๋ฅผ ์ํด 'next-client-cookies/server'์ CookiesProvider๋ฅผ RootLayout์ ๊ฐ์ธ์ฃผ์ด ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์ฟ ํค๋ค์ ๊ณต์ ํ ์ ์๊ฒํ์๋ค. ์ดํ์ Set-cookieํ ํ ํฐ๋ค์ ๊ฐ์ ๋ก๊ทธ์ธ์ ์ฝ์ด์ CookiesProvider์ ๋ฐ๋ก set ํด์ฃผ์ด ๋ฏธ๋ค์จ์ด์์ ํ ํฐ๋ค์ ์ฝ์ ์ ์๊ฒ ํ์๊ณ , ๋ง์ฐฌ๊ฐ์ง๋ก ๋ก๊ทธ์์ ์์๋ ๋ฐ๋ก CookiesProvider์ ํ ํฐ ๊ฐ๋ค์ remove ํด์ฃผ๋ ๋ก์ง์ ์ถ๊ฐํ์๋ค.
// src/middleware.ts
export async function needAuth(req: NextRequest) {
const cookies = getCookies();
const url = req.nextUrl.clone();
url.pathname = '/auth/signin';
try {
const response = await authRequest.getUser(cookies?.get('accessToken'));
if (response.status === 'SUCCESS') {
return NextResponse.next();
}
} catch (error) {
console.log('err: ', error);
return NextResponse.redirect(url);
}
}
export async function alreadyAuth(req: NextRequest) {
const cookies = getCookies();
const url = req.nextUrl.clone();
url.pathname = '/';
try {
const response = await authRequest.getUser(cookies?.get('accessToken'));
if (response.status === 'SUCCESS') {
return NextResponse.redirect(url);
}
} catch (error) {
console.log('err: ', error);
return NextResponse.next();
}
}
export function middleware(request: NextRequest) {
// <user signed> redirect to '/' when access auth pages
if (request.nextUrl.pathname.startsWith('/auth/signin')) {
console.log('call middleware - /auth/signin');
return alreadyAuth(request);
}
if (request.nextUrl.pathname.startsWith('/auth/signup')) {
console.log('call middleware - /auth/signup');
return alreadyAuth(request);
}
// <user not signed> redirect to '/auth/signin' when access pages required authentication
if (request.nextUrl.pathname.startsWith('/mypage')) {
console.log('call middleware - /mypage');
return needAuth(request);
}
if (request.nextUrl.pathname.startsWith('/cart')) {
console.log('call middleware - /cart');
return needAuth(request);
}
if (request.nextUrl.pathname.startsWith('/reservation')) {
console.log('call middleware - /reservation');
return needAuth(request);
}
if (request.nextUrl.pathname.startsWith('/reservationConfirm')) {
console.log('call middleware - /reservationConfirm');
return needAuth(request);
}
}
export const config = {
matcher: [
'/',
'/mypage',
'/cart',
'/auth/:path*',
'/reservation/:path*',
'/reservationConfirm/:path*',
],
};
// src/app/layout.tsx
const RootLayout = ({ children }: AppLayout) => (
<CookiesProvider>
<html lang='ko' className='bg-background'>
<body className='container mx-auto mb-24 max-w-3xl'>{children}</body>
</html>
</CookiesProvider>
);
๋ฐ์ฑํ
- ์ํฉ : rsuite๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ daterangepicker๋ ํ๋์ ๋ชจ๋ฌ์์ ๋๊ฐ์ ๋ฌ๋ ฅ์ ์ ๊ณตํ์ฌ ์ฌ์ฉ์๋ก ํ์ฌ๊ธ ํ๋์ ๊ธฐ๊ฐ์ ์ ํํ ์ ์๊ฒ ํฉ๋๋ค.
- ๋ฌธ์ : ํ์ง๋ง ๋ฐ์ํ์ ์ ๊ณตํ์ง ์์ ๋ชจ๋ฐ์ผ์ ๊ฒฝ์ฐ ๋ทฐํฌํธ๋ฅผ ๋ฒ์ด๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ๋ฟ๋ง ์๋๋ผ ์ฌํด 2023๋ ๋ถํฐ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ update๋ฅผ ์ค์งํ๋ฉด์ contribution๋ ํ ์ ์๋ ์ํฉ์ด์์ต๋๋ค.
- ํด๊ฒฐ : ๊ทธ๋์ css module์ ์ฌ์ฉํด์ class๋ก ์ง์ ๋ฐ์ํ์ ์ ์ฉํ์ต๋๋ค. ์ด๋ฏธ ์ ์ฉ๋ css๋ important๋ฅผ ์ ์ฉํ์ฌ ๋ฎ์ด์์ ์คํ์ผ์ ์ ์ฉํ์ต๋๋ค.
์ฝ๋
//styles/dateRangePicker.css
.rs-picker-daterange-calendar-group {
height: 100% !important;
}
@media screen and (max-width: 768px) {
.rs-picker-daterange-menu {
height: auto;
display: flex;
justify-content: center;
align-items: center;
}
.rs-picker-daterange-panel {
text-align: center;
height: 100%;
}
.rs-stack-item {
width: fit-content;
}
.rs-picker-daterange-content {
width: 100%;
}
.rs-picker-daterange-header {
width: 100%;
}
.rs-calendar-header-title {
font-weight: bold;
}
.rs-picker-daterange-calendar-group {
height: 578px;
display: flex;
flex-direction: column;
min-width: 270px !important;
}
.rs-clendar,
.rs-picker-menu {
margin: 0;
}
}
์์ง์
- ์ํฉ
์ฅ๋ฐ๊ตฌ๋ ๋ชฉ๋ก์ ์กฐํํ๋ ํ์ด์ง์์ '์์ฝ๋ถ๊ฐ์ญ์ ', '์ ํ์ญ์ ', '๊ฐ๋ณ์ญ์ ' ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํ๋ฒ ๋ ํ์ธํ๋ ์ ์ฐจ์์ด ๋ฐ๋ก ์ญ์ ๋๋ ์ํฉ - ๋ฌธ์
์ฅ๋ฐ๊ตฌ๋์ ์ถ๊ฐํ ํญ๋ชฉ์ด ํ์ธ ์ ์ฐจ์์ด ๋ฐ๋ก ์ญ์ ๋๋ค๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ๋จ์ด์ง๋ ๊ฒ์ผ๋ก ํ๋จ - ํด๊ฒฐ
์ญ์ ๋ฅผ ํ์ธํ๋ ๋ชจ๋ฌ์ ๋ง๋ค์ด์ ๋ฐ๋ก ์ญ์ ๋์ด ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ - ์ฝ๋
'use client'; import { useEffect } from 'react'; interface Props { title?: string; content?: string; cancel?: string; onCancelClick: VoidFunction; confirm?: string; onConfirmClick: VoidFunction; } const Modal = ({ title = '์ญ์ ํ์๊ฒ ์ด์?', content, cancel = '์๋์', onCancelClick, confirm = '์ญ์ ํ๊ธฐ', onConfirmClick, }: Props) => { useEffect(() => { document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = 'unset'; }; }, []); return ( <div className='fixed left-0 top-0 z-50 flex h-screen w-screen items-center justify-center bg-[rgba(0,0,0,0.5)]'> <div className='w-[18.5rem] rounded-2xl bg-white px-4 pb-2 pt-8'> <div className='mx-1 mb-4 text-center text-base font-bold text-black'> {title} </div> <div className='text-gray1 mx-1 mb-5 text-center text-sm'> {content} </div> <div className='flex items-center justify-center text-base'> <button className='text-gray1 mx-1 h-10 w-full flex-1' onClick={onCancelClick} > {cancel} </button> <button className='text-blue mx-1 h-10 flex-1 font-bold' onClick={onConfirmClick} > {confirm} </button> </div> </div> </div> ); }; export default Modal;
์ญ์ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ๋ชจ๋ฌ์ ํ์ํด์ฃผ๊ณ ํญ๋ชฉ์ ์ญ์ ํ ์ง ํ๋ฒ ๋ ํ์ธํ๋ ์ ์ฐจ๋ฅผ ๊ฑฐ์น๋๋ก ๊ฐ์const DeleteButton = ({ cartId }: Props) => { const [isShowModal, setIsShowModal] = useState(false); return ( <> <button type='button' aria-label='์ฅ๋ฐ๊ตฌ๋ ์ญ์ ' onClick={() => setIsShowModal(true)} > <HiMiniXMark className='text-gray1' /> </button> {isShowModal && ( <Modal content='์ ํํ์ ์ํ์ด ์ญ์ ๋ฉ๋๋ค' onCancelClick={() => setIsShowModal(false)} onConfirmClick={deleteCartItem} /> )} {isShowToast && <Toast message={isShowToast} />} </> ); }; export default DeleteButton;
2. api๋ก ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋์ ๋ง๋๋ก ์ ์ฒ๋ฆฌํ๋ ์ฝ๋๊ฐ ๊ธธ์ด ํ์ผ์ ๋๋ฌด ๋ง์ ์ฝ๋๋ฅผ ๋ณด์ ํ๊ณ ์๋ ๋ฌธ์
- ์ํฉ
api๋ก ๋ฐ์์จ ์ฅ๋ฐ๊ตฌ๋ ๋ฐ์ดํฐ๊ฐ ํ๋ฉด์ ๋ณด์ฌ์ค์ผํ๋ ํ์๊ณผ ์ฐจ์ด๊ฐ ์์ด ์ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผํ๋๋ฐ ๊ทธ ์ฝ๋๊ฐ ๋๋ฌด ๊ธธ์ด ํ ํ์ผ์ ๋๋ฌด ๋ง์ ๋ด์ฉ์ ๊ฐ์ง๊ณ ์๋ ์ํฉ - ๋ฌธ์
ํ ํ์ผ์ ๋๋ฌด ๋ง์ ์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์์ด์ ์ฝ๋ ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง๊ณ ์ ์ง๋ณด์๊ฐ ์ฝ์ง ์์ ๋ฌธ์ - ํด๊ฒฐ
๋ฐ์ดํฐ๋ฅผ ์ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์ปค์คํ ํ ์ผ๋ก ๋ถ๋ฆฌ - ์ฝ๋
import { useEffect, useState } from 'react'; import type { CartItemInfo, PreppedCartProduct } from '@/@types/cart.types'; const useCartList = (apiCartList: CartItemInfo[]) => { const [preppedProductList, setPreppedProductList] = useState< PreppedCartProduct[] >([]); useEffect(() => { setPreppedProductList([]); apiCartList.map((item) => { setPreppedProductList((prevPreppedProductList) => { const existingIndex = prevPreppedProductList.findIndex( (prevPreppedProductItem) => prevPreppedProductItem.productId === item.product.productId ); // ๋ฐฐ์ด ์์ ์์๊ฐ ์กด์ฌํ๋ฉด if (existingIndex !== -1) { return prevPreppedProductList.map((prevPreppedProductItem, index) => { // ์์ ์์ ๋ฐฉ๋ง ์ถ๊ฐ if (index === existingIndex) { return { ...prevPreppedProductItem, cartRoomList: [ ...prevPreppedProductItem.cartRoomList, { id: item.id, roomId: item.product.roomId, imageUrl: item.product.imageUrl, roomName: item.product.roomName, baseGuestCount: item.product.baseGuestCount, maxGuestCount: item.product.maxGuestCount, price: item.product.price, checkInTime: item.product.checkInTime, checkOutTime: item.product.checkOutTime, stock: item.product.stock, checkInDate: item.checkInDate, checkOutDate: item.checkOutDate, numberOfNights: item.numberOfNights, guestCount: item.product.guestCount, }, ], }; } return prevPreppedProductItem; }); } // ์กด์ฌํ์ง ์์ผ๋ฉด ์์ ๋ฐ ๋ฐฉ ์ถ๊ฐ return [ ...prevPreppedProductList, { productId: item.product.productId, productName: item.product.productName, address: item.product.address, cartRoomList: [ { id: item.id, roomId: item.product.roomId, imageUrl: item.product.imageUrl, roomName: item.product.roomName, baseGuestCount: item.product.baseGuestCount, maxGuestCount: item.product.maxGuestCount, price: item.product.price, checkInTime: item.product.checkInTime, checkOutTime: item.product.checkOutTime, stock: item.product.stock, checkInDate: item.checkInDate, checkOutDate: item.checkOutDate, numberOfNights: item.numberOfNights, guestCount: item.product.guestCount, }, ], }, ]; }); }); }, [apiCartList]); return preppedProductList; }; export default useCartList;
์ฅ๋ฌธ์ฉ
-
์์ ๊ฐ์๋ฅผ ๋ถ๋ฌ์ค๋ ์ค์ ๋ฐ์ํ ์ค๋ฅ๋ฅผ ํด๊ฒฐํ์ต๋๋ค. ์ด ๋ฌธ์ ์ ์์ธ์ API ๋ฌธ์์์ ํ์ด์ง์ ๊ธฐ๋ณธ๊ฐ์ด 1๋ก ๋ช ์๋์ด ์์ด์, ์ด๊ธฐ์๋ ํ์ด์ง๊ฐ 1๋ถํฐ ์์ํ๋ค๊ณ ์คํดํ๊ณ ์์๋ ๊ฒ์ ๋๋ค. ํ์ง๋ง ์ค์ ๋ก๋ ํ์ด์ง๊ฐ 0๋ถํฐ ์์ํ๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค. ์ด๋ก ์ธํด ๋ฐ์ํ ์ค๋ฅ๋ฅผ ์์ ํ์ต๋๋ค
-
๋ฉ์ธ์บ๋ฌ์ ์ฝ๋์์ ์๋
any
ํ์ ์ ๋ช ์์ ์ธ TypeScript ํ์ ์ผ๋ก ๋์ฒดํ์์ต๋๋ค. -
์ปดํฌ๋ํธ
main
,products
ํด๋์index
ํ์ผ์ ์ถ๊ฐํ์์ต๋๋ค. -
์ปดํฌ๋ํธ์
section
ํ๊ทธ ๋ฃ์ด์main
ํ์ด์ง ์ฝ๋๋ฅผ ์ ๋ฆฌํ์์ต๋๋ค. -
๋ ์ง ๊ด๋ จ ๊ธฐ๋ฅ์ ๋ํ ๋ถ๋ถ์ ์ ํธ๋ฆฌํฐ ํจ์ ์ ์ฉ์ผ๋ก ๊ฐ์ ํ์์ต๋๋ค.
-
ํ๋ก์ ํธ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ์ฌ ํ์ ์ ์๋ฅผ
types
ํด๋๋ก, ๊ทธ๋ฆฌ๊ณ API ์์ฒญ๊ณผ ๊ด๋ จ๋ ๋ก์ง์api
ํด๋๋ก ์ด๋์์ผฐ์ต๋๋ค. ์ด ๋ณ๊ฒฝ์ ์ฝ๋์ ๋ชจ๋ํ์ ๊ฐ๋ ์ฑ์ ํฅ์์ํค๊ณ , ์ ์ง๋ณด์์ฑ์ ๊ฐํํ๋ ๋ฐ ๋ชฉ์ ์ด ์์ต๋๋ค.
์ ์ง์ฃผ
- ์์ฝ ๋ด์ญ ๋ชฉ๋ก ํ์ธ ๋ฌดํ์คํฌ๋กค ์ ์ฉ
- ์ฃผ๋ฌธ ๋ด์ญ ๋ชฉ๋ก, ์ฃผ๋ฌธ ๋ด์ญ ๋ํ ์ผ ํ์ด์ง์์์๋ ๋ช ๋ฐ์ธ์ง ๋ณด์ด๊ฒ ๋ณ๊ฒฝ
- ๋ฐ๊ฒฌํ์ง ๋ชป ํ anyํ์ ์ ๊ฑฐ
- ๋ถ๋ช ํํ ๋ณ์, ํจ์๋ช ์์
- ๊ธฐ๊ฐ ์ต๊ทผ 6๊ฐ์ fix
- ์ฃผ๋ฌธ ๋ด์ญ ๋ชฉ๋ก์์ CARD, CASH -> ์นด๋, ๊ณ์ข์ด์ฒด ํ๊ธ๋ก ๋ณด์ด๊ฒ ๋ณ๊ฒฝ
- ๋ฒํผ ๋๋ฅด๋ฉด ์ ์ถ๋๋ ๋ฒ๊ทธ ๋ณ๊ฒฝ