# React Spa

> React SPA (Vite/CRA) 프로젝트 컨벤션. react-router-dom v6 라우팅, 빌드/개발 설정.

- **Type:** Skill
- **Install:** `agentstack add skill-ggombee-code-forge-react-spa`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ggombee](https://agentstack.voostack.com/s/ggombee)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ggombee](https://github.com/ggombee)
- **Source:** https://github.com/ggombee/code-forge/tree/main/modules/frameworks/react-spa

## Install

```sh
agentstack add skill-ggombee-code-forge-react-spa
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# React SPA 컨벤션

이 프로젝트는 **Vite** 또는 **CRA(Create React App)** 기반의 React SPA다.

---

## 프로젝트 구조

```
src/
├── main.tsx            # 진입점
├── App.tsx             # 루트 컴포넌트
├── routes/             # 라우트 정의
│   └── index.tsx
├── pages/              # 페이지 컴포넌트
│   ├── HomePage.tsx
│   └── OrderPage.tsx
├── components/         # 공유 컴포넌트
├── hooks/              # 커스텀 훅
├── services/           # API 서비스
├── store/              # 상태 관리
├── types/              # TypeScript 타입
└── utils/              # 유틸리티 함수
```

---

## Vite 설정

```typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: process.env.VITE_API_URL,
        changeOrigin: true,
      },
    },
  },
});
```

---

## 진입점

```typescript
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
import './index.css';

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')!).render(
  
    
      
        
      
    
  ,
);
```

---

## react-router-dom v6 라우팅

### 기본 라우트 설정

```typescript
// src/routes/index.tsx
import { Routes, Route, Navigate } from 'react-router-dom';
import { Suspense, lazy } from 'react';

// 코드 스플리팅
const HomePage = lazy(() => import('@/pages/HomePage'));
const OrderPage = lazy(() => import('@/pages/OrderPage'));
const OrderDetailPage = lazy(() => import('@/pages/OrderDetailPage'));
const NotFoundPage = lazy(() => import('@/pages/NotFoundPage'));

export function AppRoutes() {
  return (
    로딩 중...}>
      
        } />
        } />
        } />
        } />
        } />
      
    
  );
}
```

### 중첩 라우트

```typescript
// src/routes/index.tsx
import { Outlet } from 'react-router-dom';

function DashboardLayout() {
  return (
    
      대시보드 메뉴
       {/* 자식 라우트가 렌더링되는 위치 */}
    
  );
}

export function AppRoutes() {
  return (
    
      }>
        } />       {/* /dashboard */}
        } /> {/* /dashboard/orders */}
        } /> {/* /dashboard/settings */}
      
    
  );
}
```

### 보호된 라우트

```typescript
// src/routes/ProtectedRoute.tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuthStore } from '@/store/auth';

export function ProtectedRoute() {
  const isAuthenticated = useAuthStore((state) => state.isAuthenticated);

  if (!isAuthenticated) {
    return ;
  }

  return ;
}
```

### 라우트 훅 사용

```typescript
import { useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom';

function OrderDetailPage() {
  const { orderId } = useParams();
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const location = useLocation();

  const handleBack = () => navigate(-1);
  const handleGoToOrders = () => navigate('/orders');

  // 쿼리 파라미터
  const page = searchParams.get('page') ?? '1';

  return 주문 #{orderId};
}
```

---

## 환경 변수 (Vite)

```typescript
// .env.local
VITE_API_URL=http://localhost:8080
VITE_ENV=development

// 코드에서 사용
const apiUrl = import.meta.env.VITE_API_URL;
const isDev = import.meta.env.DEV; // Vite 내장 변수
```

```typescript
// ❌ process.env는 Vite에서 동작하지 않음
const url = process.env.REACT_APP_API_URL; // CRA 방식

// ✅ Vite 방식
const url = import.meta.env.VITE_API_URL;
```

---

## 빌드 설정

```json
// package.json
{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint src --ext ts,tsx",
    "test": "vitest"
  }
}
```

---

## App.tsx 구조

```typescript
// src/App.tsx
import { AppRoutes } from '@/routes';

function App() {
  return ;
}

export default App;
```

---

## 체크리스트

- [ ] 코드 스플리팅에 `lazy()` + `Suspense` 사용?
- [ ] 환경 변수는 `VITE_` 접두사 사용 (Vite)?
- [ ] 중첩 라우트에 `Outlet` 사용?
- [ ] 보호된 라우트에 `ProtectedRoute` 래퍼 사용?
- [ ] 존재하지 않는 경로는 404 페이지로 리다이렉트?

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ggombee](https://github.com/ggombee)
- **Source:** [ggombee/code-forge](https://github.com/ggombee/code-forge)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-ggombee-code-forge-react-spa
- Seller: https://agentstack.voostack.com/s/ggombee
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
