# Fe CLI Web

> Scaffold a Web SPA frontend project. For websites, landing pages, and standard single-page apps. Triggered as a sub-skill of fe-cli when user wants a web/SPA project type.

- Skill: `z-zihan/fe-cli-web` (Agent Skill)
- Install (CLI): `npx skillmds@latest add z-zihan/fe-cli-web`
- Raw SKILL.md: https://api.skillmd.com/api/skills/z-zihan/fe-cli-web/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: z-Zihan (https://skillmd.com/u/z-zihan)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/z-zihan/fe-cli-web

---


# fe-cli-web — Web SPA Scaffolding

Handles project scaffolding for standard Web Single Page Applications.

## Workflow

### Step 1: Gather Options

Ask these questions. Skip questions whose answer is implied by the user's initial request.

**Quick mode:** If user provides all options in one message (e.g. "React + Tailwind + Zustand + i18n，叫 my-site"),
skip ALL questions and proceed directly to Step 2.

1. **Framework**: React 19 / Vue 3
2. **Styling/UI**: Tailwind CSS / shadcn/ui + Lucide (React) / shadcn-vue + Vue Bits (Vue) / Ant Design (React) / Element Plus (Vue) / MUI (React) / Chakra UI (React) / React Bits (React) / 纯 CSS (SCSS)
3. **CSS Preprocessor**: Sass / Less / None (default: Sass if using SCSS approach)
4. **State Management**: Zustand / Redux Toolkit / Pinia (for Vue) / None
5. **Router**: React Router / Vue Router / None
6. **Charts**: Recharts (lightweight) / ECharts (heavy) / None
7. **i18n**: react-i18next / vue-i18n / None
8. **Testing**: Vitest / None
9. **Pre-commit hooks**: husky + lint-staged + commitlint? (default: No)
10. **Project name**: string (required)

### Step 2: Scaffold Project

Use `bun create vite` (or `pnpm create vite` if bun unavailable) as the base:

```
bun create vite <project-name> --template react-ts   (for React)
bun create vite <project-name> --template vue-ts     (for Vue)
```

Then `cd <project-name>` and install additional dependencies based on selections.

### Step 3: Install Dependencies

| Selection | Packages |
|---|---|
| Tailwind CSS | `tailwindcss @tailwindcss/vite` |
| shadcn/ui (React) | `npx shadcn@latest init` → adds components on demand. Requires Tailwind CSS. Include `lucide-react` |
| shadcn-vue (Vue) | `npx shadcn-vue@latest init` → adds components on demand. Requires Tailwind CSS. Include `lucide-vue-next` |
| Ant Design | `antd @ant-design/icons @ant-design/v5-patch-for-react-19` |
| Element Plus | `element-plus @element-plus/icons-vue` |
| MUI | `@mui/material @emotion/react @emotion/styled @mui/icons-material` |
| Chakra UI | `@chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons` |
| React Bits | `react-bits` (animated UI components) |
| Vue Bits | `vue-bits` (animated UI components for Vue) |
| Zustand | `zustand` |
| Redux Toolkit | `@reduxjs/toolkit react-redux` |
| React Router | `react-router-dom` |
| Vue Router | `vue-router` |
| Recharts | `recharts` |
| ECharts | `echarts echarts-for-react` (React) / `vue-echarts` (Vue) |
| i18n (React) | `react-i18next i18next i18next-browser-languagedetector` |
| i18n (Vue) | `vue-i18n` |
| Vitest | `vitest @testing-library/react @testing-library/jest-dom jsdom` (React) |
| Sass | `sass` (built-in Vite support) |
| Less | `less` |
| pre-commit | `husky lint-staged @commitlint/cli @commitlint/config-conventional` |

### Step 4: Configure Vite

Generate `vite.config.ts` based on selections. Use the template from `../references/shared-config.md`.
Add Tailwind plugin if selected: `import tailwindcss from '@tailwindcss/vite'` → `plugins: [react(), tailwindcss()]`

### Step 5: Generate Type-Specific Files

**React projects:**

```
src/
├── components/
│   ├── ErrorBoundary.tsx     # React error boundary with fallback UI
│   ├── PageLoading.tsx       # Full-page loading spinner
│   ├── EmptyState.tsx        # Empty state placeholder
│   └── ErrorState.tsx        # Error state with retry button
├── pages/
│   ├── Home/
│   │   └── index.tsx         # Home page
│   └── NotFound.tsx          # 404 page
├── hooks/                    # Common hooks (generated by shared layer)
├── layouts/                  # Layout components (generated by shared infrastructure)
├── store/                    # State management (generated by shared infrastructure)
├── theme/                    # Theme system (generated by shared infrastructure)
├── config/                   # App constants + routes (generated by shared infrastructure)
├── locales/                  # i18n (generated by shared infrastructure, if selected)
├── App.tsx                   # App with router setup + layout
└── main.tsx                  # Entry point
```

`App.tsx` template:
```tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from '@/pages/Home';
import NotFound from '@/pages/NotFound';

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}
```

`components/ErrorBoundary.tsx`:
```tsx
import { Component, type ReactNode } from 'react';
import ErrorState from './ErrorState';

interface Props { children: ReactNode; }
interface State { hasError: boolean; error?: Error; }

export default class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false };
  static getDerivedStateFromError(error: Error) { return { hasError: true, error }; }
  render() {
    if (this.state.hasError) return <ErrorState error={this.state.error} />;
    return this.props.children;
  }
}
```

`components/PageLoading.tsx`:
> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.
```tsx
// Pure CSS spinner — works regardless of UI library choice
export default function PageLoading() {
  return (
    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
      <div style={{
        width: 32, height: 32, border: '3px solid #e8e8e8', borderTopColor: '#1677ff',
        borderRadius: '50%', animation: 'spin 0.8s linear infinite',
      }} />
      <style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
    </div>
  );
}
```

`components/EmptyState.tsx`:
> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.
```tsx
interface Props { description?: string; }
export default function EmptyState({ description = '暂无数据' }: Props) {
  return <div style={{ textAlign: 'center', padding: '48px 0', color: '#999' }}>{description}</div>;
}
```

`components/ErrorState.tsx`:
> **Note:** Inline styles below are for quick scaffolding. **Production environments should replace them with SCSS modules**.
```tsx
interface Props { error?: Error; onRetry?: () => void; }
export default function ErrorState({ error, onRetry }: Props) {
  return (
    <div style={{ textAlign: 'center', padding: '48px 0' }}>
      <p style={{ color: '#ff4d4f', marginBottom: 16 }}>{error?.message || '页面出错了'}</p>
      {onRetry && <button onClick={onRetry}>重试</button>}
    </div>
  );
}
```

> **Note:** `hooks/useRequest.ts` and other common hooks are generated by the shared infrastructure layer (Step 6).
> See `../references/shared-base.md` for `useRequest` template and `../references/shared-infrastructure.md` for all other hooks.

**Vue projects:** Adapt same patterns to Vue 3 Composition API (defineComponent, ref, reactive, etc.)

### Step 6: Generate Shared Layer

After type-specific files, read the following reference files and generate all shared code:
1. `../references/shared-base.md` — services, utils, styles, types, env files
2. `../references/shared-config.md` — vite, tsconfig, eslint, prettier configs
3. `../references/shared-infrastructure.md` — store, theme, i18n, hooks, layouts, auth guard, config/constants

**Conditional generation** (only if user selected the corresponding option):
- Selected Zustand/Redux Toolkit/Pinia → generate `src/store/`
- Selected i18n → generate `src/locales/`
- **Always generate** (regardless of options): `src/hooks/` (common hooks), `src/components/AppProvider.tsx`, `src/components/AuthGuard.tsx`, `src/components/GlobalLoading.tsx`, `src/config/`, `src/theme/`

### Step 7: Final Setup

```bash
cd <project-name>
bun install  # or: pnpm install
bun dev      # or: pnpm dev
```

Announce completion: project name, framework, key dependencies, dev server URL (http://localhost:5173).

**Vercel deployment** (recommended):
```bash
bun add -g vercel
vercel
```
Or connect GitHub repo at vercel.com for auto-deploy.

