목적: 팀 프론트엔드 프로젝트를 시작할 때 각 기술의 역할/개념/필수 용어/설정 포인트/실무 주의사항을 한 문서로 정리했다.
범위: 아래 7개 스택의 “기본적으로 알아야 하는 내용”을 가능한 한 빠짐없이 포함했다.
import 하는 모듈을 Vite dev server가 그때그때 변환해서 제공.dist/에 산출물 생성(설정에 따라 변경 가능).vite.config.ts에서 플러그인/빌드 옵션/별칭(alias)/프록시(proxy) 등을 설정한다.base: 서브 디렉토리 배포 시 정적 리소스 경로 대응server.proxy: 로컬에서 API 프록시(CORS 회피)resolve.alias: @/ 별칭 통일예시(개념용):
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
base: "/",
plugins: [react()],
resolve: {
alias: {
"@": "/src",
},
},
server: {
proxy: {
"/api": {
target: "https://example.com",
changeOrigin: true,
},
},
},
});
import.meta.env를 사용한다.VITE_ prefix가 필요하다.VITE_API_BASE_URL, VITE_APP_BASENAMEindex.html 리라이트 설정이 없어 새로고침 404.env 수정 후 dev 서버 재시작 안 해서 환경변수 반영 안 됨props로 입력을 받고 JSX를 반환한다.useState 등으로 관리.useEffect는 렌더 결과가 화면에 반영된 뒤 실행된다.useState에 복사해서 들고 있기보다 React Query로 관리하는 편이 안전하다(싱크 이슈 감소). setCount((prev) => prev + 1);
useEffect 의존성 배열을 잘못 잡아서 무한 루프A | B, A & Btype 선호interface 고려 type ButtonProps = {
label: string;
onClick: () => void;
disabled?: boolean;
};
export function Button(props: ButtonProps) {
return (
<button disabled={props.disabled} onClick={props.onClick}>
{props.label}
</button>
);
}
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
type User = {
id: string;
name: string;
};
async function fetchUser(): Promise<User> {
const res = await fetch("/api/user");
if (!res.ok) throw new Error("Failed");
return (await res.json()) as User;
}
any 남발 금지(가능하면 unknown + 타입가드)as Type로 단언하면 런타임 오류를 놓칠 수 있음(필요 시 검증 레이어 고려)@tailwindcss/vite)bg-black text-white px-4 py-2 roundedsrc/index.css에 아래처럼 단순하게 시작하는 형태가 많다. @import "tailwindcss";
@tailwindcss/vite 플러그인을 통해 이뤄진다. <div className="p-4 md:p-8 lg:p-12">...</div>
<button className="bg-black text-white hover:opacity-80 disabled:opacity-40">
Save
</button>
<div className="bg-white text-black dark:bg-neutral-900 dark:text-white">
...
</div>
cn() 유틸로 조건부 결합createBrowserRouter)createBrowserRouter는 라우트 트리를 객체로 선언하고 RouterProvider로 주입한다. import { createBrowserRouter } from "react-router-dom";
import App from "./App";
export const router = createBrowserRouter([
{ path: "/", element: <App /> },
{ path: "/about", element: <div>안녕~</div> },
]);
import { RouterProvider } from "react-router-dom";
import { router } from "./routes";
export function Root() {
return <RouterProvider router={router} />;
}
path와 element로 매핑<Outlet />로 자식 렌더React 기본 state로 서버 데이터를 관리하면:
React Query는 이걸 규칙으로 해결한다.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export function Root() {
return (
<QueryClientProvider client={queryClient}>
{/* router 등 */}
</QueryClientProvider>
);
}
import { useQuery } from "@tanstack/react-query";
type User = { id: string; name: string };
async function fetchUsers(): Promise<User[]> {
const res = await fetch("/api/users");
if (!res.ok) throw new Error("Failed to fetch users");
return (await res.json()) as User[];
}
export function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 60_000,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return (
<ul>
{data?.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
import { useMutation, useQueryClient } from "@tanstack/react-query";
async function createUser(name: string) {
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
});
if (!res.ok) throw new Error("Failed to create user");
return res.json();
}
export function CreateUser() {
const qc = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: async () => {
await qc.invalidateQueries({ queryKey: ["users"] });
},
});
return <button onClick={() => mutation.mutate("Kim")}>Add</button>;
}
["liveReserve", "list", params])babel-plugin-react-compiler)useMemo, useCallback, React.memo를 “언제 붙일지” 덜 고민해도 되게.babel-plugin-react-compiler로 포함된다.bun install / bun run dev 동작 확인VITE_ prefix 확인 + dev 서버 재시작