webdev
A guide to organizing frontend code using Feature-Sliced Design (FSD) for better modularity.
Install
mkdir -p .claude/skills/webdev && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19068" && unzip -o skill.zip -d .claude/skills/webdev && rm skill.zipInstalls to .claude/skills/webdev
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Guidelines for TypeScript, React, and Next.js development.Key capabilities
- →Organize frontend code using Feature-Sliced Design (FSD) layers
- →Enforce import restrictions between FSD layers
- →Partition code into slices by business domain within layers
- →Organize code within slices using segments for technical purpose
- →Export functionality through public API `index.ts` barrel files
- →Use ES module syntax for TypeScript imports and exports
How it works
This skill provides guidelines for organizing frontend code using Feature-Sliced Design, which structures code into layers, slices, and segments based on business domain and technical purpose. It enforces strict import rules between these architectural components.
Inputs & outputs
When to use webdev
- →Structuring new frontend modules
- →Refactoring code to FSD standards
- →Enforcing architecture boundaries
About this skill
Web Development Skill
Guidelines for TypeScript, React, and Next.js development.
Code Organization: Feature-Sliced Design
All frontend code must follow Feature-Sliced Design (FSD) — an architectural methodology that organizes code by business domain rather than technical concerns.
Layers
FSD defines standardized layers, ordered top to bottom. Modules may only import from layers strictly below them. No lateral imports between modules on the same layer.
| Layer | Purpose | Examples |
|---|---|---|
| app | Runtime essentials: routing, entry points, global styles, providers | app/layout.tsx, app/providers.tsx |
| pages | Full pages or major routed views | pages/dashboard, pages/calculator-editor |
| widgets | Large self-contained UI blocks composed of features and entities | widgets/calculator-preview, widgets/submission-list |
| features | Reusable product feature implementations that deliver business value | features/create-calculator, features/apply-theme |
| entities | Business domain objects — data models, their UI representations, and API calls | entities/calculator, entities/submission, entities/user |
| shared | Reusable, project-agnostic code: UI kit, utilities, API client, config | shared/ui, shared/lib, shared/api |
Slices
Within each layer (except app and shared), code is partitioned into slices by business domain. Each slice is a folder named after its domain concept.
Critical rule: Slices on the same layer cannot import from each other. This enforces low coupling.
features/
├── create-calculator/ # One slice
├── apply-theme/ # Another slice — cannot import from create-calculator
└── configure-formula/ # Another slice — cannot import from siblings
Segments
Each slice contains segments that organize code by technical purpose:
| Segment | Contents |
|---|---|
| ui | React components, styles |
| model | TypeScript types/interfaces, stores, business logic |
| api | Backend interaction, data fetching, request/response types |
| lib | Internal utilities specific to this slice |
| config | Constants, feature flags, configuration |
Public API
Every slice must export through an index.ts barrel file at its root. External consumers import only from this public API, never from internal segment files.
// features/create-calculator/index.ts — public API
export { CreateCalculatorForm } from './ui/CreateCalculatorForm';
export { useCreateCalculator } from './model/useCreateCalculator';
export type { CreateCalculatorParams } from './model/types';
// ✅ Good — import from the slice's public API
import { CreateCalculatorForm } from '@/features/create-calculator';
// ❌ Bad — reaching into internal segments
import { CreateCalculatorForm } from '@/features/create-calculator/ui/CreateCalculatorForm';
Example Project Structure
src/
├── app/ # App layer: Next.js routing, providers, global styles
│ ├── layout.tsx
│ ├── providers.tsx
│ ├── globals.css
│ ├── dashboard/
│ │ └── page.tsx
│ └── editor/[id]/
│ └── page.tsx
│
├── pages/ # Pages layer: page-level compositions
│ ├── dashboard/
│ │ ├── ui/
│ │ ├── model/
│ │ └── index.ts
│ └── calculator-editor/
│ ├── ui/
│ ├── model/
│ └── index.ts
│
├── widgets/ # Widgets layer: self-contained UI blocks
│ ├── calculator-preview/
│ │ ├── ui/
│ │ ├── model/
│ │ └── index.ts
│ └── submission-table/
│ ├── ui/
│ ├── model/
│ └── index.ts
│
├── features/ # Features layer: user-facing capabilities
│ ├── create-calculator/
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
│ │ └── index.ts
│ ├── configure-formula/
│ │ ├── ui/
│ │ ├── model/
│ │ └── index.ts
│ └── apply-theme/
│ ├── ui/
│ ├── model/
│ └── index.ts
│
├── entities/ # Entities layer: business domain objects
│ ├── calculator/
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
│ │ └── index.ts
│ ├── submission/
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
│ │ └── index.ts
│ └── user/
│ ├── model/
│ ├── api/
│ └── index.ts
│
└── shared/ # Shared layer: project-agnostic reusable code
├── ui/ # UI kit (buttons, inputs, modals)
├── api/ # API client, interceptors
├── lib/ # Utilities (formatting, validation)
└── config/ # Constants, env config
Import Rules Summary
app → pages, widgets, features, entities, shared
pages → widgets, features, entities, shared
widgets → features, entities, shared
features → entities, shared
entities → shared
shared → (external packages only)
Violations of these import rules indicate an architectural problem. Fix the dependency direction rather than working around it.
TypeScript
ES Modules
Always use ES module syntax (import/export) instead of CommonJS (require/module.exports).
Bad - CommonJS:
const nextJest = require('next/jest');
module.exports = config;
Good - ES Modules:
import nextJest from 'next/jest.js';
export default config;
Important Notes:
- When importing from npm packages in ESM mode, you may need to include the
.jsextension (e.g.,'next/jest.js') - Use
import typefor TypeScript types to enable type-only imports - Use named exports for utilities, default exports for components/configs
Never Use any Types
The any type defeats the purpose of TypeScript and should be avoided. Use proper type assertions or type guards instead.
Bad:
const result = convertFormat(input, options as any, output as any);
Good:
import type { ParseOptions, FormatOptions } from "./types";
const inputOptions: ParseOptions = { format: format as DateTimeFormat, customFormat };
const outputOptions: FormatOptions = { format: format as DateTimeFormat };
const result = convertFormat(input, inputOptions, outputOptions);
When you need type assertions:
- Use specific type assertions:
value as string,value as DateTimeFormat - Import and use the actual types from their definitions
- Document why the assertion is safe with a comment if non-obvious
Alternatives to any:
unknown- for truly unknown types (forces type checking before use)- Union types -
string | number | boolean - Generic types -
<T>with constraints - Type guards -
if (typeof x === 'string')
Component Props Typing
Use interfaces or types for component props:
interface MessageProps {
/** The message content to display */
content: string;
/** Whether the message is from the current user */
isOwn: boolean;
/** Timestamp when the message was sent */
timestamp: Date;
/** Optional callback when message is clicked */
onClick?: (messageId: string) => void;
}
function Message({ content, isOwn, timestamp, onClick }: MessageProps) {
return (
<div onClick={() => onClick?.(content)}>
<p>{content}</p>
<time>{timestamp.toISOString()}</time>
</div>
);
}
Hook Typing Patterns
useState - Let TypeScript infer when possible:
// Inferred as boolean
const [isOpen, setIsOpen] = useState(false);
// Union types need explicit typing
type Status = "idle" | "loading" | "success" | "error";
const [status, setStatus] = useState<Status>("idle");
// Discriminated unions for complex state
type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Message[] }
| { status: 'error'; error: Error };
const [state, setState] = useState<RequestState>({ status: 'idle' });
useReducer - Type actions as discriminated unions:
interface State {
messages: Message[];
status: Status;
}
type Action =
| { type: "messages_loaded"; messages: Message[] }
| { type: "message_added"; message: Message }
| { type: "message_deleted"; messageId: string }
| { type: "status_changed"; status: Status };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "messages_loaded":
return { ...state, messages: action.messages, status: "success" };
case "message_added":
return { ...state, messages: [...state.messages, action.message] };
case "message_deleted":
return {
...state,
messages: state.messages.filter(m => m.id !== action.messageId)
};
case "status_changed":
return { ...state, status: action.status };
}
}
useContext - Handle nullable contexts:
type User = { id: string; name: string; role: string };
const UserContext = createContext<User | null>(null);
function useUser() {
const user = useContext(UserContext);
if (!user) {
throw new Error("useUser must be used within UserProvider");
}
return user;
}
Event handlers:
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
setValue(event.currentTarget.value);
}
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
// ...
}
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
// ...
}
Common React types:
interface ComponentProps {
children: React.ReactNode; // Any valid React child
style?: React.CSSProperties; // Inline styles
className?: string;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}
React State Management
State Structure Principles
1. Group related state
// ❌ Bad - separate state that updates together
const [x, setX] = useState(0);
const [y, setY] = useState(0);
// ✅ Good - group
---
*Content truncated.*
When not to use it
- →When organizing code by technical concerns instead of business domain
- →When importing laterally between modules on the same FSD layer
- →When using CommonJS syntax for TypeScript
Limitations
- →Modules may only import from layers strictly below them
- →Slices on the same layer cannot import from each other
- →External consumers must import only from a slice's public API, never from internal segment files
How it compares
This workflow uses Feature-Sliced Design to organize code by business domain with strict import rules, unlike a generic approach that might organize code by technical type or allow unrestricted imports.
Compared to similar skills
webdev side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| webdev (this skill) | 0 | 5mo | No flags | Advanced |
| nextjs-best-practices | 31 | 6mo | No flags | Intermediate |
| project-overview | 15 | 1mo | No flags | Beginner |
| nx-generate | 1 | 6mo | Review | Beginner |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
nextjs-best-practices
davila7
Next.js App Router principles. Server Components, data fetching, routing patterns.
project-overview
lobehub
Complete project architecture and structure guide. Use when exploring the codebase, understanding project organization, finding files, or needing comprehensive architectural context. Triggers on architecture questions, directory navigation, or project overview needs.
nx-generate
nrwl
Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
javascript-typescript-typescript-scaffold
sickn33
You are a TypeScript project architecture expert specializing in scaffolding production-ready Node.js and frontend applications. Generate complete project structures with modern tooling (pnpm, Vite, N
server-components
davepoon
This skill should be used when the user asks about "Server Components", "Client Components", "'use client' directive", "when to use server vs client", "RSC patterns", "component composition", "data fetching in components", or needs guidance on React Server Components architecture in Next.js.
supabase-architecture-variants
jeremylongshore
Execute choose and implement Supabase validated architecture blueprints for different scales. Use when designing new Supabase integrations, choosing between monolith/service/microservice architectures, or planning migration paths for Supabase applications. Trigger with phrases like "supabase architecture", "supabase blueprint", "how to structure supabase", "supabase project layout", "supabase microservice".