# State Management Standards for Shadcn
This document outlines the standards for managing application state in Shadcn projects. It covers various approaches, best practices, and common pitfalls to avoid, focusing on maintainability, performance, and a consistent development experience. This complements the component-level coding standard for Shadcn.
## 1. Choosing a State Management Solution
Shadcn provides a robust foundation for building UIs, but it does not prescribe a specific state management library. The choice of state management solution depends on the complexity of your application.
### 1.1 Standard: Use Context API + "useReducer" for simple state management
* **Do This:** Start with the built-in React Context API combined with the "useReducer" hook for simple state management needs within isolated parts of your application.
* **Don't Do This:** Immediately reach for a large state management library (like Redux or Zustand) for simple applications. This adds unnecessary complexity.
**Why:** React's built-in Context API is sufficient for managing state in isolated components or smaller applications, providing a lightweight and straightforward solution without introducing external dependencies. The "useReducer" hook is a better alternative of "useState" hook for state management of more complex components.
**Example:**
"""jsx
// ThemeContext.jsx
import React, { createContext, useReducer, useContext } from 'react';
const ThemeContext = createContext();
const initialState = {
darkMode: false,
};
const reducer = (state, action) => {
switch (action.type) {
case 'TOGGLE_DARK_MODE':
return { ...state, darkMode: !state.darkMode };
default:
return state;
}
};
const ThemeProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
{children}
);
};
const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
};
export { ThemeProvider, useTheme };
"""
"""jsx
// ComponentUsingTheme.jsx
import React from 'react';
import { useTheme } from './ThemeContext';
import { Button } from "@/components/ui/button"
const ComponentUsingTheme = () => {
const { state, dispatch } = useTheme();
return (
<p>Dark Mode: {state.darkMode ? 'On' : 'Off'}</p>
dispatch({ type: 'TOGGLE_DARK_MODE' })}>
Toggle Dark Mode
);
};
export default ComponentUsingTheme;
"""
### 1.2 Standard: Use Zustand for moderate complexity
* **Do This:** Consider Zustand for global state management in medium-sized applications for its simplicity and minimal boilerplate.
* **Don't Do This:** Use Redux or similar verbose state management libraries when Zustand can achieve the same results with less code.
**Why:** Zustand offers a simple and unopinionated approach to state management. It's easy to learn and integrate, making it ideal for managing global application state without the complexity of larger libraries.
**Example:**
"""jsx
// store.js
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
export default useStore;
"""
"""jsx
// CounterComponent.jsx
import React from 'react';
import useStore from './store';
import { Button } from "@/components/ui/button"
const CounterComponent = () => {
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
const decrement = useStore((state) => state.decrement);
const reset = useStore((state) => state.reset)
return (
<p>Count: {count}</p>
Increment
Decrement
Reset
);
};
export default CounterComponent;
"""
### 1.3 Standard: Use Redux Toolkit **only** for complex global application state
* **Do This:** Reserve Redux Toolkit for very complex applications that benefit from its structured approach, middleware, and debugging tools. Only use Redux with Redux Toolkit.
* **Don't Do This:** Use Redux without Redux Toolkit. Redux without RTK is too verbose and difficult to manage.
**Why:** Redux, especially with Redux Toolkit, provides a predictable state container that's beneficial for large, complex applications. It includes features like middleware for handling asynchronous actions, and tools for debugging and time-traveling state.
**Example:**
"""jsx
// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
"""
"""jsx
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
"""
"""jsx
// CounterComponent.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';
import { Button } from "@/components/ui/button"
const CounterComponent = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<p>Count: {count}</p>
dispatch(increment())}>Increment
dispatch(decrement())}>Decrement
dispatch(incrementByAmount(5))}>Increment by 5
);
};
export default CounterComponent;
"""
### 1.4 Standard: Use React Query/Tanstack Query for server state management
* **Do This:** Use React Query (now known as TanStack Query) for managing server state, caching, and background updates.
* **Don't Do This:** Use local state or Redux to cache server data manually. React Query is specifically designed for this purpose and handles complexities automatically.
**Why:** React Query simplifies fetching, caching, synchronizing, and updating server state. It offers built-in features for caching, background updates, retries, and more, reducing boilerplate and improving performance.
**Example:**
"""jsx
// usePosts.js
import { useQuery } from '@tanstack/react-query';
const fetchPosts = async () => {
const response = await fetch('/api/posts');
if (!response.ok) {
throw new Error('Failed to fetch posts');
}
return response.json();
};
const usePosts = () => {
return useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
};
export default usePosts;
"""
"""jsx
// PostsComponent.jsx
import React from 'react';
import usePosts from './usePosts';
const PostsComponent = () => {
const { data: posts, isLoading, error } = usePosts();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
{posts.map((post) => (
{post.title}
))}
);
};
export default PostsComponent;
"""
## 2. Data Flow and Reactivity
### 2.1 Standard: Prefer unidirectional data flow.
* **Do This:** Ensure data flows in one direction, typically from parent components to children via props, and updates flow back to the parent through callbacks or state management solutions. Avoid two-way data binding.
* **Don't Do This:** Mutate props directly in child components. This makes debugging harder and leads to unpredictable behavior.
**Why:** Unidirectional data flow increases predictability, makes debugging easier, and simplifies reasoning about application state changes.
**Example (Correct):**
"""jsx
// ParentComponent.jsx
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [message, setMessage] = useState('Hello');
const handleMessageChange = (newMessage) => {
setMessage(newMessage);
};
return (
<p>Parent Message: {message}</p>
);
};
export default ParentComponent;
"""
"""jsx
// ChildComponent.jsx
import React from 'react';
import { Input } from "@/components/ui/input"
const ChildComponent = ({ message, onMessageChange }) => {
const handleChange = (event) => {
onMessageChange(event.target.value);
};
return (
);
};
export default ChildComponent;
"""
**Example (Incorrect - Prop Mutation):**
"""jsx
// IncorrectChildComponent.jsx
import React from 'react';
import { Input } from "@/components/ui/input"
const IncorrectChildComponent = ({ message }) => {
const handleChange = (event) => {
//DO NOT DO THIS
message = event.target.value; // Directly mutating prop
};
return (
);
};
export default IncorrectChildComponent;
"""
### 2.2 Standard: Utilize keys for dynamic lists.
* **Do This:** Provide unique "key" props when rendering dynamic lists of elements. The key be a stable and predictable value related to THAT ITEM (e.g. item id).
* **Don't Do This:** Use array indices as keys, especially if the order of the list items might change. Also don't use randomly generated UUIDS because those change on every render.
**Why:** Keys help React efficiently update the DOM when items are added, removed, or reordered. Using incorrect keys (or no keys) degrades performance and can cause unexpected behavior.
**Example:**
"""jsx
// Correct
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
];
const ItemList = () => {
return (
{items.map((item) => (
{item.name}
))}
);
};
export default ItemList;
"""
"""jsx
// Incorrect
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
];
const BadItemList = () => {
return (
{items.map((item, index) => (
{item.name}
))}
);
};
export default BadItemList;
"""
### 2.3 Standard: Optimize Re-renders with "React.memo"
* **Do This:** Use "React.memo" to prevent unnecessary re-renders of components when their props haven't changed. Shadcn provides visually polished components; avoiding unnecessary re-renders on key UI elements is paramount.
* **Don't Do This:** Wrap every component with "React.memo" without profiling first. This can add overhead if the component re-renders infrequently or if the prop comparison is expensive.
**Why:** "React.memo" optimizes performance by skipping re-renders for components when the props haven't changed.
**Example:**
"""jsx
import React from 'react';
const MyComponent = ({ data, onClick }) => {
console.log('MyComponent re-rendered');
return (
{data.name}
);
};
export default React.memo(MyComponent, (prevProps, nextProps) => {
// Custom comparison function (optional)
// Return true if props are equal, false if props are different
return prevProps.data.id === nextProps.data.id;
});
"""
Without the second argument (the equality function), "React.memo" will do a shallow comparison of the props. If complex props are used (like objects), consider providing the custom equality function for a precise comparison.
### 2.4 Standard: Use "useCallback" and "useMemo" Hooks
* **Do This:** Utilize "useCallback" and "useMemo" hooks for optimizing performance by memoizing functions and values that are passed as props to children components.
* **Don't Do This:** Overuse "useCallback" and "useMemo" without careful profiling. It is a common mistake to wrap every possible function/value. Also do not forget the dependency arrays of these functions.
**Why:** "useCallback" and "useMemo" prevent recreating functions and values on every render. This improves performance by ensuring that child components only re-render when their props have actually changed.
**Example:**
"""jsx
import React, { useState, useCallback, useMemo } from 'react';
import MyComponent from './MyComponent';
const ParentComponent = () => {
const [count, setCount] = useState(0);
// Memoize the data object
const data = useMemo(() => ({ id: 1, name: 'Item' }), []);
// Memoize the onClick function
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<p>Count: {count}</p>
setCount(count + 1)}>Increment Parent
);
};
export default ParentComponent;
"""
In this setup, "handleClick" is only recreated when "count" changes, and "data" is only created once preventing unnecessary renders of "MyComponent".
## 3. Shadcn Specific State Management Considerations
### 3.1 Standard: Manage component-level styling state within the component.
* **Do This:** For simple UI interactions like toggling a component's visibility or changing its appearance, manage the state directly within the component using "useState".
**Why:** This keeps the component self-contained and reduces complexity. Avoid unnecessary state management overhead for simple UI interactions.
**Example:**
"""jsx
import React, { useState } from 'react';
import { Button } from "@/components/ui/button"
const ToggleComponent = () => {
const [isVisible, setIsVisible] = useState(false);
return (
setIsVisible(!isVisible)}>
{isVisible ? 'Hide' : 'Show'}
{isVisible && <p>This content is visible.</p>}
);
};
export default ToggleComponent;
"""
### 3.2 Standard: Use Zod for schema validation of form data
* **Do This:** Use Zod for validating forms that leverage Shadcn component. Zod has first class Typescript support. This ensures data accuracy and consistency, improving the user experience and the reliability of the application.
* **Don't Do This:** Write custom validation functions or regular expressions without leveraging a validation library, as it can be error-prone and hard to maintain.
**Why:** Zod offers a declarative and type-safe way to define schemas and validate data. It integrates well with TypeScript, providing excellent developer experience.
**Example:**
"""jsx
import { z } from "zod"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
email: z.string().email({
message: "Invalid email address.",
}),
})
export default formSchema
"""
### 3.3 Standard: Use "useTransition" for Optimistic UI Updates.
* **Do This:** When performing actions that update server state, use the "useTransition" hook to provide an optimistic UI update. This makes the UI feel more responsive.
* **Don't Do This:** Block the UI while waiting for server responses which creates a slow user experience.
**Why:** Optimistic UI updates provide immediate feedback to the user, improving the perceived performance and responsiveness of the application.
**Example:**
"""jsx
import React, { useState, useTransition } from 'react';
import { Button } from "@/components/ui/button"
const LikeButton = ({ postId }) => {
const [isLiked, setIsLiked] = useState(false);
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(() => {
setIsLiked(!isLiked);
// Simulate an API call
setTimeout(() => {
console.log("API call completed");
}, 500);
});
};
return (
{isLiked ? 'Unlike' : 'Like'} {isPending ? 'Updating...' : ''}
);
};
export default LikeButton;
"""
## 4. Anti-Patterns to Avoid
### 4.1 Anti-Pattern: Prop Drilling
* **Avoid This:** Passing props through multiple layers of components when only a deeply nested component needs the data. This can hinder component reusability and makes the code hard to maintain.
**Solution:** Use Context API, Zustand, or Redux to provide the data where it’s needed without passing it through intermediate components.
### 4.2 Anti-Pattern: Over-reliance on Global State
* **Avoid This:** Storing everything in global state. Managing component-specific state locally can lead to unnecessary re-renders throughout the application.
**Solution:** Identify which state truly needs to be global and which can be managed locally within components or using Context API for a specific component tree.
### 4.3 Anti-Pattern: Ignoring React Query Optimizations
* **Avoid This:** Neglecting React Query's features like "staleTime", "cacheTime", and "refetchOnWindowFocus".
**Solution:** Configure React Query's options based on your application's needs. For frequently updated data, a shorter "staleTime" is appropriate. For less frequently updated data, a longer "staleTime" can reduce unnecessary API calls. Consider disabling "refetchOnWindowFocus" if updates are not needed when the user switches back to the tab.
### 4.4 Anti-Pattern: Mutating State Directly
* **Avoid This:** Directly modifying state variables without using state update functions (e.g., "useState"'s "setState", Redux reducers, Zustand's "set").
**Solution:** Always use the proper state update mechanisms to trigger re-renders and maintain predictable state changes.
## 5. Community Standards and Patterns
### 5.1 Standard: Atomic State Updates
* **Do This:** When updating state that depends on the previous state, use the functional form of "setState" in "useState" or the updater function in "useReducer" or Zustand.
**Why:** This ensures that you are working with the correct previous state, avoiding potential race conditions or stale data issues, especially when dealing with asynchronous updates.
**Example:**
"""jsx
// useState with functional update
import React, { useState } from 'react';
import { Button } from "@/components/ui/button"
const CounterComponent = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
return (
<p>Count: {count}</p>
Increment
);
};
export default CounterComponent;
"""
### 5.2 Standard: Colocation of State Logic
* **Do This:** Keep state logic close to where it's used. If a component's state logic becomes complex, consider moving it into a custom hook.
**Why:** This improves code organization, testability, and maintainability.
**Example:**
"""jsx
// useCounter.js
import { useState } from 'react';
const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
const decrement = () => {
setCount(prevCount => prevCount - 1);
};
return { count, increment, decrement };
};
export default useCounter;
"""
"""jsx
// CounterComponent.jsx
import React from 'react';
import useCounter from './useCounter';
import { Button } from "@/components/ui/button"
const CounterComponent = () => {
const { count, increment, decrement } = useCounter(10);
return (
<p>Count: {count}</p>
Increment
Decrement
);
};
export default CounterComponent;
"""
### 5.3 Standard: Graceful Degradation/Error Handling
* **Do This:** When fetching data, especially with React Query, handle loading and error states gracefully. Display informative messages or fallback content to provide a good user experience even when things go wrong.
* **Don't Do This:** Display blank screens or unhandled errors to the user.
**Why:** Proper error handling makes your application more robust and user-friendly.
**Example:**
"""jsx
import React from 'react';
import usePosts from './usePosts';
const PostsComponent = () => {
const { data: posts, isLoading, error } = usePosts();
if (isLoading) return <p>Loading posts...</p>;
if (error) return <p>Error fetching posts: {error.message}</p>;
return (
{posts.map((post) => (
{post.title}
))}
);
};
export default PostsComponent;
"""
## 6. Performance Optimization Techniques
### 6.1 Standard: Code Splitting
* **Do This:** Implement code splitting using dynamic imports ("React.lazy" and "Suspense") to reduce the initial load time of your application. Defer loading non-critical components.
**Why:** Code splitting improves performance by loading only the code that is needed for the current view, thus reducing the initial bundle size.
**Example:**
"""jsx
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
const MyComponent = () => {
return (
Loading...}>
);
};
export default MyComponent;
"""
### 6.2 Standard: Virtualization of large lists
* **Do This:** Use libraries like "react-window" or "react-virtualized" to efficiently render large lists of data.
**Why:** Virtualization renders only the visible items in the list, significantly improving performance for long lists.
**Example:**
"""jsx
import React from 'react';
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
Row {index}
);
const VirtualizedList = () => {
return (
{Row}
);
};
export default VirtualizedList;
"""
### 6.3 Standard: Debouncing and Throttling
* **Do This:** Use debouncing and throttling techniques to limit the rate at which functions are executed, particularly for event handlers that fire rapidly (e.g., "onChange" on an input field).
* **Don't Do This:** Execute expensive operations on every event.
**Why:** Debouncing and throttling improve performance by reducing the number of function calls, which is especially important for optimizing user input handling and API calls.
**Example:**
"""jsx
import React, { useState, useCallback } from 'react';
import { debounce } from 'lodash';
import { Input } from "@/components/ui/input"
const SearchComponent = () => {
const [searchTerm, setSearchTerm] = useState('');
const handleSearch = (value) => {
console.log('Searching for:', value);
// Perform API call or other expensive operation here
};
const debouncedSearch = useCallback(
debounce((value) => {
handleSearch(value);
}, 300),
[]
);
const handleChange = (event) => {
const { value } = event.target;
setSearchTerm(value);
debouncedSearch(value);
};
return (
);
};
export default SearchComponent;
"""
## 7. Security Considerations for State Management
### 7.1 Standard: Limit State Exposure
* **Do This:** Avoid storing sensitive information (e.g., passwords, API keys, personal data) directly in client-side state if it's not absolutely necessary. If you must store sensitive data temporarily, encrypt it and clear it from the state as soon as it's no longer needed.
* **Don't Do This:** Store sensitive data in plaintext in local storage or cookies.
**Why:** Minimizing the exposure of sensitive data reduces the risk of it being compromised through XSS attacks or other vulnerabilities.
### 7.2 Standard: Sanitize User Inputs
* **Do This:** Sanitize and validate all user inputs before storing them in the state. This helps prevent XSS attacks and other vulnerabilities.
* **Don't Do This:** Directly use user inputs without any form of validation or sanitization which could be a security risk.
**Why:** Sanitizing user inputs ensures that malicious scripts or code cannot be injected into your application through user-controlled data.
### 7.3 Standard: Secure API Communication
* **Do This:** Use HTTPS for all API communication to encrypt data in transit. Implement proper authentication and authorization mechanisms to ensure that only authorized users can access and modify state. Use secure cookies with the "HttpOnly" and "Secure" flags.
* **Don't Do This:** Use HTTP for API communication.
**Why:** HTTPS encrypts the data exchanged between the client and the server, protecting it from eavesdropping and tampering. Proper authentication and authorization ensure that only authorized users can modify application state.
### 7. 4 Standard: Prevent State Injection
* **Do This:** Protect against state injection attacks by ensuring that only authorized code can modify the application's state. Use immutable data structures to prevent unauthorized modifications of state.
* **Don't Do This:** Allow external scripts or unauthorized code to directly modify the application state.
**Why:** Protecting against unauthorized state modifications can prevent malicious actors from manipulating the state, leading to unexpected behavior or data breaches.
This updated document provides a comprehensive guide to state management in Shadcn projects, covering various approaches, best practices, and security considerations. Adhering to these guidelines will result in more maintainable, performant, and secure applications.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Deployment and DevOps Standards for Shadcn This document outlines the best practices and coding standards for Deployment and DevOps within Shadcn projects. It aims to guide developers in building reliable, scalable, and maintainable applications that efficiently utilize the Shadcn ecosystem. These standards should be used as context for AI coding assistants like GitHub Copilot, Cursor, and similar tools to ensure consistency and quality across the codebase. ## 1. Build Processes and CI/CD Pipelines ### 1.1. Standard: Automated Builds with CI/CD **Do This:** Implement a CI/CD pipeline for automated builds, testing, and deployment. Use tools like GitHub Actions, GitLab CI, CircleCI, or Jenkins. **Don't Do This:** Manually build and deploy changes to production. **Why:** Automation reduces human error, provides faster feedback loops, and ensures consistent deployment across environments. **Example (GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install Dependencies run: npm ci - name: Build run: npm run build - name: Deploy to Vercel # Or Netlify, AWS, etc. run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} """ **Anti-pattern:** Manually running "npm run build" and copying the output to a server. ### 1.2. Standard: Versioning and Tagging **Do This:** Use semantic versioning (SemVer) for releases. Automatically tag Git commits with version numbers during the CI/CD process. **Don't Do This:** Use arbitrary versioning schemes or skip versioning altogether. **Why:** Semantic versioning provides clarity on the nature and scope of changes, facilitating smoother upgrades and dependency management. **Example (Versioning in "package.json"):** """json { "name": "my-shadcn-app", "version": "1.2.3", "description": "A fantastic Shadcn application", "main": "index.js", "scripts": { "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "lucide-react": "^0.303.0", "next": "14.0.4", "react": "18.2.0", "react-dom": "18.2.0", "tailwind-merge": "^2.2.0", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@types/node": "20.10.6", "@types/react": "18.2.46", "@types/react-dom": "18.2.18", "autoprefixer": "^10.4.16", "eslint": "8.56.0", "eslint-config-next": "14.0.4", "postcss": "^8.4.32", "typescript": "5.3.3" } } """ **Anti-pattern:** Skipping the "npm version" command and manually updating the "package.json". ### 1.3. Standard: Environment Variables Management **Do This:** Use environment variables for configuration. Store secrets in a secure vault (like HashiCorp Vault, AWS Secrets Manager, or Vercel/Netlify environment variables). Access environment variables using a consistent mechanism. Consider using a library like "zod" with "next-safe-action" to ensure type-safe validation of environment variables. **Don't Do This:** Hardcode sensitive information in the codebase or commit it to the repository. **Why:** Properly managed environment variables allow for easy configuration changes across different environments (development, staging, production) without modifying the code. **Example (Environment Variables with Zod and next-safe-action):** First, install "next-safe-action" and "zod": "npm install next-safe-action zod" """typescript // lib/env.ts import { z } from "zod"; const envSchema = z.object({ DATABASE_URL: z.string().min(1), NEXTAUTH_SECRET: z.string().min(1), NEXTAUTH_URL: z.string().url(), GITHUB_CLIENT_ID: z.string().min(1), GITHUB_CLIENT_SECRET: z.string().min(1), }); const _env = envSchema.safeParse(process.env); if (_env.success === false) { console.error( "Invalid environment variables:\n", _env.error.format() ); throw new Error("Invalid environment variables"); } export const env = _env.data; """ """typescript // src/app/actions.ts 'use server' // Ensure this is a server action import { createSafeActionClient } from "next-safe-action"; import { z } from "zod"; import { env } from "@/lib/env"; const safeAction = createSafeActionClient(); const inputSchema = z.object({ name: z.string().min(2), email: z.string().email() }); export const submitForm = safeAction(inputSchema, async (data) => { // Access environment variables in a type-safe way console.log("DATABASE_URL: ${env.DATABASE_URL}"); console.log("User Data: ${data.name}, ${data.email}"); // Simulate database operation await new Promise((resolve) => setTimeout(resolve, 1000)); return {message: "Form submitted successfully by ${data.name}!"}; }); """ **Anti-pattern:** Directly using "process.env.API_KEY" throughout the application without validation or default values. Committing ".env" files to the repository. ### 1.4. Standard: Build Artifact Caching **Do This:** Cache build artifacts and dependencies during CI/CD to speed up build times (e.g., using "actions/cache" in GitHub Actions or similar features in other CI tools). **Don't Do This:** Reinstall dependencies and rebuild everything from scratch on every CI/CD run. **Why:** Caching significantly reduces build times, leading to faster deployment cycles and reduced resource consumption. **Example (Caching dependencies in GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Cache dependencies uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install Dependencies run: npm ci - name: Build run: npm run build - name: Deploy to Vercel # Or Netlify, AWS, etc. run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} """ **Anti-pattern:** Not caching dependencies or build outputs, leading to slow and inefficient builds. ## 2. Production Considerations ### 2.1. Standard: Monitoring and Alerting **Do This:** Implement comprehensive monitoring and alerting for your application. Use tools like Prometheus, Grafana, Datadog, or New Relic. Monitor key metrics such as response time, error rates, CPU usage, and memory consumption. Integrate Shadcn components (progress indicators, status messages) within your monitoring dashboards. **Don't Do This:** Deploy to production without any monitoring in place. **Why:** Monitoring allows you to quickly identify and resolve issues in production, ensuring high availability and a smooth user experience. Alerting ensures you are notified promptly when something goes wrong. **Example (Basic Error Logging with Shadcn UI Feedback:)** """typescript // src/components/ErrorDisplay.tsx import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { XCircle } from "lucide-react"; // Assuming you're using Lucide interface ErrorDisplayProps { errorMessage: string; } const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ errorMessage }) => { if (!errorMessage) { return null; } return ( <Alert variant="destructive"> <XCircle className="h-4 w-4" /> <AlertTitle>Error</AlertTitle> <AlertDescription>{errorMessage}</AlertDescription> </Alert> ); }; export default ErrorDisplay; // Usage: try { // Some code that might throw an error const result = await fetchData(); if (!result) { throw new Error("Failed to fetch data"); } } catch (error:any) { console.error("Error occurred:", error); // Log the error on the server // Set state or pass to error display component setErrorMessage(error.message || "An unexpected error occurred."); // Inform user with Shadcn Alert } """ **Anti-pattern:** Relying solely on user reports to identify issues in production. ### 2.2. Standard: Logging **Do This:** Implement structured logging with appropriate levels (debug, info, warning, error). Use a logging library like Winston or Bunyan. Aggregate logs using a centralized logging system (e.g., ELK stack, Graylog, or Splunk). **Don't Do This:** Use "console.log" for all logging in production. **Why:** Centralized logging allows you to easily search, analyze, and correlate logs across different services, making it easier to diagnose and troubleshoot issues. **Example (Using Winston for structured logging):** """javascript // logger.js const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), defaultMeta: { service: 'my-shadcn-app' }, transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ] }); if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() })); } module.exports = logger; // Usage: const logger = require('./logger'); try { // Some code that might throw an error } catch (error) { logger.error('An error occurred', { error: error, message: error.message }); } """ **Anti-pattern:** Scattering "console.log" statements throughout the codebase without any structure or context. ### 2.3. Standard: Performance Optimization **Do This:** Optimize application performance by: - Code Splitting: Implement code splitting to reduce initial load times. - Lazy Loading: Lazy load non-critical components and images. - Server-Side Rendering (SSR) or Static Site Generation (SSG): Use SSR or SSG for improved SEO and performance. Next.js (often used with Shadcn) supports these natively. - Caching: Implement caching at various levels (browser, CDN, server-side). - Image Optimization: Optimize images for web delivery (e.g., using "next/image"). **Don't Do This:** Ignore performance issues until they become critical. **Why:** Performance optimization improves user experience, reduces server load, and lowers infrastructure costs. **Example (Lazy Loading with Shadcn components integrated):** """jsx import React, { Suspense } from 'react'; import { Skeleton } from "@/components/ui/skeleton" const LazyComponent = React.lazy(() => import('./components/HeavyComponent')); function MyPage() { return ( <div> {/* Other content */} <Suspense fallback={<Skeleton className="w-[400px] h-[200px]" />}> <LazyComponent /> </Suspense> </div> ); } export default MyPage; """ **Anti-pattern:** Loading large JavaScript bundles on initial page load, causing slow initial rendering. ### 2.4. Standard: Security **Do This:** Implement security best practices, including: - Input Validation: Validate all user inputs to prevent injections. Use a library like Zod to guarantee your inputs are valid. - Authentication and Authorization: Secure authentication and authorization mechanisms. Use NextAuth.js or similar libraries. - HTTPS: Enforce HTTPS for all connections. - Regular Security Audits: Conduct regular security audits and penetration testing. - Dependency Updates: Keep dependencies up-to-date to patch known vulnerabilities. **Don't Do This:** Store sensitive information in client-side code or cookies. **Why:** Security is paramount to protect user data and prevent malicious attacks. **Example (Sanitzing text input):** """typescript import { z } from "zod" const schema = z.object({ title: z.string().min(3).max(255), body: z.string().min(10) }) function validate(input: unknown) { return schema.safeParse(input) } """ **Anti-pattern:** Exposing API keys or database credentials in client-side JavaScript or publicly accessible configuration files. ## 3. Shadcn-Specific Considerations ### 3.1. Standard: Component Versioning and Upgrades **Do This:** Keep Shadcn components up-to-date. Regularly check for updates and apply them using the "shadcn-ui" CLI. Test upgraded components thoroughly. **Don't Do This:** Use outdated versions of Shadcn components without applying security or bug fixes. **Why:** Upgrading Shadcn components ensures you benefit from the latest features, performance improvements, and security patches. **Example (Updating Shadcn components):** """bash npx shadcn-ui@latest update button """ ### 3.2. Standard: Customization and Theming **Do This:** Utilize Tailwind CSS variables and Shadcn's theming capabilities. Create reusable component variants using "class-variance-authority" (cva) to maintain consistency. **Don't Do This:** Directly modify the underlying CSS of Shadcn components without using Tailwind's utility classes or theming. **Why:** Leveraging Tailwind and Shadcn's theming ensures a consistent and maintainable design system. **Example (component variants using cva):** """typescript import { cva } from "class-variance-authority"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ); """ ### 3.3 Standard: Shadcn Component Overrides **Do This:** When overriding Shadcn components, ensure you are using CSS composition and Tailwind utility classes to maintain consistency with the base theme. Comment clearly when you override default Shadcn styles. **Don't Do This:** Use !important or overly specific CSS selectors to override styles as it introduces maintenance overhead. **Why**: Using proper CSS Composition and Tailwind utility classes helps maintain the themes and improves long-term maintainability of the codebase. """typescript /** * Override: Increasing the padding of the button to make it bigger. * You should document why a component is overridden. */ <Button className="px-8 py-4"> Click me </Button> """ ## 4. Modern Approaches and Patterns ### 4.1. Standard: Infrastructure as Code (IaC) **Do This:** Define and manage infrastructure using code (e.g., Terraform, AWS CloudFormation, or Pulumi, CDK). Automate infrastructure provisioning and configuration. **Don't Do This:** Manually provision and configure infrastructure resources. **Why:** IaC ensures consistent and repeatable infrastructure deployments, reduces manual errors, and enables version control for infrastructure changes. ### 4.2. Standard: Containerization **Do This:** Containerize applications using Docker. Use container orchestration platforms like Kubernetes or Docker Swarm for deployment and scaling. **Don't Do This:** Deploy applications directly to virtual machines without containerization. **Why:** Containerization provides isolation, portability, and scalability, simplifying deployment and management. ### 4.3. Standard: Serverless Functions **Do This:** Utilize serverless functions (e.g., AWS Lambda, Azure Functions, or Google Cloud Functions) for event-driven tasks and lightweight APIs. **Don't Do This:** Run all application logic in monolithic servers. **Why:** Serverless functions offer scalability, cost efficiency, and simplified operational management. ### 4.4 Standard: Edge Computing **Do This:** Consider leveraging edge computing platforms (e.g., Cloudflare Workers, Vercel Edge Functions, Netlify Edge Functions) to execute code closer to users for reduced latency. **Don't Do This:** Assume all processing must occur within the central application servers. **Why:** Edge computing enhances performance by minimizing network latency and improving geographically distributed user experiences. ## 5. Conclusion Adhering to these Deployment and DevOps standards will help ensure your Shadcn projects are reliable, scalable, secure, and maintainable. By following these guidelines, development teams can build high-quality applications that deliver exceptional user experiences. Regular reviews and updates to these standards are essential to keep pace with the evolving landscape of web development and the continuous improvements within the Shadcn ecosystem.
# Code Style and Conventions Standards for Shadcn This document outlines the code style and conventions to be followed when developing with Shadcn. Adhering to these standards ensures consistency, readability, maintainability, and performance across the entire codebase. These guidelines are designed to be used by developers and as context for AI coding assistants, promoting a unified approach to Shadcn development. ## 1. Formatting and General Style ### 1.1. Code Formatting * **Standard:** Use a consistent code formatter like Prettier. Configure it to enforce consistent indentation (2 spaces), line length (120 characters), and trailing commas. * **Why:** Consistent formatting improves readability and reduces unnecessary changes in diffs. * **Do This:** """javascript // .prettierrc.js module.exports = { semi: true, trailingComma: 'all', singleQuote: true, printWidth: 120, tabWidth: 2, useTabs: false, }; """ * **Don't Do This:** Manually format code inconsistently. Relying solely on developer preference leads to a cluttered codebase. * **Standard:** Integrate Prettier with ESLint (or similar linter) to automatically fix formatting issues. * **Why:** Automating formatting reduces cognitive load and ensures compliance. * **Do This:** """bash npm install --save-dev prettier eslint-plugin-prettier eslint-config-prettier """ """javascript // .eslintrc.js module.exports = { extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier', ], plugins: ['react', '@typescript-eslint', 'prettier'], rules: { 'prettier/prettier': 'error', // Add other ESLint rules here }, }; """ * **Don't Do This:** Skip linter integration. This forces developers to manually address style issues during code reviews. ### 1.2. File Structure * **Standard:** Organize components into directories based on functionality (e.g., "components/ui", "components/features"). Use the "components" directory as the root for all Shadcn UI components and custom components. * **Why:** Clear file structure enhances navigation and code discoverability. * **Do This:** """ src/ ├── components/ │ ├── ui/ │ │ ├── button.tsx │ │ ├── card.tsx │ │ └── ... │ ├── features/ │ │ ├── product-list.tsx │ │ ├── checkout-form.tsx │ │ └── ... │ └── layout/ │ ├── header.tsx │ └── footer.tsx ├── app/ │ ├── page.tsx │ └── ... └── lib/ ├── utils.ts └── ... """ * **Don't Do This:** Dump all components into a single directory, making it harder to find and manage. * **Standard:** Favor colocating related files (component, styles, tests) within the same directory. Also include stories for Storybook if using it. * **Why:** Easier to find and maintain related code. * **Do This:** """ src/components/ui/button/ ├── button.tsx ├── button.module.css //Or tailwind classes in the component ├── button.test.tsx └── button.stories.tsx """ * **Don't Do This:** Scatter related files across the project. ### 1.3. Imports and Exports * **Standard:** Use absolute imports for internal modules, with "@" as a root alias (configured in "tsconfig.json"). Use relative imports only for files within the same component directory. * **Why:** Prevents brittle import paths and improves code refactorability. * **Do This:** """typescript // tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] } } } """ """typescript // Correct Usage (absolute import) import { Button } from '@/components/ui/button'; // Correct Usage (relative import, within the same component directory) import styles from './button.module.css'; """ * **Don't Do This:** """typescript // Avoid: Relative imports from distant directories import { Button } from '../../../../components/ui/button'; """ * **Standard:** Group imports by origin (third-party libraries, internal modules, relative paths). Separate each group with a blank line. Order imports alphabetically within each group. * **Why:** Improves readability and makes it easier to identify dependencies. * **Do This:** """typescript import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { cn } from '@/lib/utils'; import { Input } from '@/components/ui/input'; import styles from './form.module.css'; """ * **Don't Do This:** Mix imports randomly; it obfuscates dependencies. ### 1.4. Comments and Documentation * **Standard:** Write clear, concise comments to explain complex logic, algorithms, or non-obvious code sections. Focus on *why* the code is written, not *what* it does. * **Why:** Clear documentation helps other developers (including your future self) understand the codebase. * **Do This:** """typescript /** * Calculates the total price of items in the cart, * applying any applicable discounts. * * @param {OrderItem[]} cartItems - An array of items in the cart. * @returns {number} The total price after discounts. */ function calculateTotalPrice(cartItems: OrderItem[]): number { // Apply a 10% discount for orders over $100. const basePrice = cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0); const discount = basePrice > 100 ? basePrice * 0.1 : 0; return basePrice - discount; } """ * **Don't Do This:** Over-comment obvious code, skip commenting complex parts. * **Standard:** Use JSDoc or TSDoc style comments for documenting components and functions. This allows tools like Storybook and documentation generators to automatically create API documentation. * **Why:** Enables automated API documentation and improves discoverability. * **Do This:** """typescript /** * A styled button component. * * @param {ButtonProps} props - The props for the button. * @returns {JSX.Element} A button element. */ export function Button({ children, ...props }: ButtonProps): JSX.Element { return ( <button {...props} className={cn("button", props.className)}> {children} </button> ); } """ * **Standard:** Keep documentation up-to-date with code changes. Outdated documentation is worse than no documentation. * **Why:** Ensures accuracy and relevance of documentation over time. ## 2. Naming Conventions ### 2.1. Variables and Constants * **Standard:** Use descriptive, camelCase names for variables and functions (e.g., "userName", "calculateTotalPrice"). * **Why:** Improves readability and clarity. * **Do This:** """typescript const userEmail = 'test@example.com'; function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) { // ... } """ * **Don't Do This:** Use short, cryptic names like "x", "y", or single-letter variable names. * **Standard:** Use SCREAMING_SNAKE_CASE for constants (e.g., "MAX_ITEMS", "API_URL"). * **Why:** Clearly distinguishes constants from variables. * **Do This:** """typescript const API_URL = 'https://api.example.com'; const MAX_RETRIES = 3; """ * **Don't Do This:** Use camelCase for constants. ### 2.2. Components * **Standard:** Use PascalCase for component names (e.g., "UserProfile", "ProductCard"). * **Why:** Consistent with React conventions and makes components easily identifiable. * **Do This:** """typescript function ProductCard(props: ProductCardProps): JSX.Element { // ... } """ * **Don't Do This:** Use camelCase or snake_case; it violates React component naming standards. * **Standard:** When creating UI components using Shadcn UI, stick to names related to the function it provides. Try to be as semantic as possible. Example : "LoginButton", "SideBarNavigation", "ProductCardImage" * **Standard:** Name component files the same as the component (e.g., "ProductCard.tsx" for the "ProductCard" component). * **Why:** Simplifies file lookup and improves project structure. * **Do This:** A "ProductCard" component should be placed in "ProductCard.tsx" file. * **Don't Do This:** Name files arbitrarily or inconsistently. ### 2.3. Hooks * **Standard:** Use "use" prefix for custom React hooks (e.g., "useUserData", "useFetch"). * **Why:** Follows React convention, making it clear that a function is a hook. * **Do This:** """typescript function useUserData(userId: string) { // ... } """ * **Don't Do This:** Omit the "use" prefix; it violates Hook naming standards set by React. ### 2.4. CSS Classes * **Standard:** Use a consistent naming convention for CSS classes, preferably BEM (Block, Element, Modifier) or a similar methodology. If using Tailwind CSS, leverage its utility-first approach directly in the component. * **Why:** Improves CSS maintainability and reduces naming conflicts. * **Do This (BEM):** """css .product-card { /* Block styles */ } .product-card__title { /* Element styles */ } .product-card--featured { /* Modifier styles */ } """ * **Do This (Tailwind CSS):** """tsx <div className="bg-white rounded-lg shadow-md p-4"> <h2 className="text-xl font-bold mb-2">Product Title</h2> <p className="text-gray-700">Product description...</p> </div> """ * **Don't Do This:** Use vague or generic class names without any methodology (e.g., "red", "small", "box"). ### 2.5 Type Names * **Standard:** Use PascalCase and the "Type" or "Interface" suffix when declare types for components like "ButtonType", "CardInterface". * **Why:** Improve readability of the code. * **Do This:** """typescript interface ButtonInterface{ variant: "default"|"destructive", size: "sm" | "lg", name: string } """ ## 3. Shadcn-Specific Conventions ### 3.1. Component Composition * **Standard:** Leverage Shadcn UI's composable components to build complex UIs. Avoid creating new low-level components if possible. * **Why:** Promotes consistency and reusability, and reduces code duplication. * **Do This:** """typescript import { Button } from '@/components/ui/button'; import { Card, CardHeader, CardBody } from '@/components/ui/card'; function UserProfileCard() { return ( <Card> <CardHeader>User Profile</CardHeader> <CardBody> <p>Name: John Doe</p> <Button>Edit Profile</Button> </CardBody> </Card> ); } """ * **Don't Do This:** Rebuild basic UI elements from scratch when Shadcn UI already provides them. ### 3.2. Customization with "cn" Utility * **Standard:** Use the "cn" utility (from "class-variance-authority") to easily combine Tailwind CSS classes and handle conditional class names. * **Why:** Simplifies class name management and improves readability. * **Do This:** """typescript import { cn } from '@/lib/utils'; interface ButtonProps { variant?: 'primary' | 'secondary'; size?: 'small' | 'large'; className?: string; children: React.ReactNode; } function Button({ variant = 'primary', size = 'small', className, children }: ButtonProps) { return ( <button className={cn( 'rounded-md font-semibold', { 'bg-blue-500 text-white': variant === 'primary', 'bg-gray-200 text-gray-700': variant === 'secondary', 'px-2 py-1 text-sm': size === 'small', 'px-4 py-2 text-base': size === 'large', }, className )} > {children} </button> ); } """ * **Don't Do This:** Manually concatenate class names with string interpolation; it becomes unwieldy and error-prone. ### 3.3. Overriding Styles * **Standard:** Use CSS variables or Tailwind CSS's customization features to override the default styles of Shadcn UI components. * **Why:** Preserves consistency and makes it easier to maintain a consistent design system. * **Do This (CSS Variables):** """css :root { --button-primary-bg: #007bff; } .button.primary { background-color: var(--button-primary-bg); } """ * **Do This (Tailwind CSS Customization):** In "tailwind.config.js": """javascript module.exports = { theme: { extend: { colors: { primary: '#007bff', secondary: '#6c757d', }, }, }, }; """ Then, in the component: """tsx <button className="bg-primary text-white font-bold py-2 px-4 rounded"> Click me </button> """ * **Don't Do This:** Use inline styles or overly-specific CSS selectors to override styles, as it reduces maintainability and increases the risk of conflicts. ### 3.4 Data Fetching and State Management * **Standard:** When fetching data for components, prefer using React Query or SWR. * **Why:** Handle data fetching and caching with a performatic way. * **Do This:** """tsx import { useQuery } from "@tanstack/react-query"; interface Props { id: string } function UserProfile({id}:Props) { const{isLoading, data:profile} = useQuery({ queryKey: ['profile',id], queryFn: ()=>fetch('/user/'+id).then(res=>res.json()) }) if(isLoading){ return <div>Loading...</div> } return <div>{profile?.name}</div> } """ ## 4. Modern Approaches and Patterns ### 4.1. Functional Components and Hooks * **Standard:** Prefer functional components and hooks over class components for new code. * **Why:** Functional components are simpler, more readable, and easier to test. Hooks promote code reuse and separation of concerns. * **Do This:** """typescript import { useState, useEffect } from 'react'; function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = "Count: ${count}"; }, [count]); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); } """ * **Don't Do This:** Use class components for new feature development without a specific reason. ### 4.2. TypeScript * **Standard:** Use TypeScript for all new code. Annotate components, functions, and variables with types. * **Why:** TypeScript improves code quality, reduces runtime errors, and enhances maintainability. * **Do This:** """typescript interface User { id: number; name: string; email: string; } function greetUser(user: User): string { return "Hello, ${user.name}!"; } """ * **Don't Do This:** Skip type annotations; it negates the benefits of TypeScript. ### 4.3 Utility Functions * **Standard:** Store utility functions in "lib/utils.ts" or similar dedicated files. * **Why:** Allow use the same function across all the project. * **Do This:** """typescript // lib/utils.ts import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } """ ## 5. Anti-Patterns to Avoid ### 5.1. Over-Engineering * **Anti-Pattern:** Creating overly complex solutions for simple problems. Avoid premature optimization or unnecessary abstraction. * **Why:** Increases code complexity and reduces maintainability. ### 5.2. Prop Drilling * **Anti-Pattern:** Passing props through multiple layers of components without being directly used. * **Why:** Makes code harder to understand and refactor. * **Solution:** Use context or state management libraries like Zustand or Redux to share data between components. ### 5.3. Mutating Props Directly * **Anti-Pattern:** Modifying props directly within a component. * **Why:** Violates React's unidirectional data flow and can lead to unpredictable behavior. * **Solution:** Use state to manage component-specific data and pass down event handlers to update the parent's state. ### 5.4. Inefficient Rendering * **Anti-Pattern:** Causing unnecessary re-renders by not memoizing components or using inefficient data structures. * **Why:** Impacts performance, especially in complex UIs. * **Solution:** Use "React.memo", "useMemo", and "useCallback" to optimize rendering. ### 5.5. Ignoring Accessibility * **Anti-Pattern:** Neglecting accessibility considerations (e.g., proper ARIA attributes, semantic HTML) when building UI components. * **Why:** Excludes users with disabilities and violates accessibility standards (WCAG). * **Solution:** Use semantic HTML, provide ARIA attributes where necessary, and test with assistive technologies. ## 6. Security Best Practices ### 6.1. Input Validation * **Standard:** Always validate user inputs on both the client and server sides. * **Why:** Prevents security vulnerabilities such as XSS and SQL injection. * **Do This:** """typescript // Client-side validation function validateEmail(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } // Server-side validation (using a library like Zod) import { z } from 'zod'; const userSchema = z.object({ email: z.string().email(), password: z.string().min(8), }); function createUser(data: any) { const validatedData = userSchema.parse(data); // ... } """ ### 6.2. Secure Authentication * **Standard:** Use industry-standard authentication and authorization mechanisms such as OAuth 2.0 and JWT. * **Why:** Protects user accounts and data. ### 6.3. Data Sanitization * **Standard:** Sanitize data before rendering it in the UI to prevent XSS attacks. * **Why:** Prevents malicious scripts from executing in the user's browser. * **Do This:** """typescript function escapeHtml(unsafe: string) { return unsafe .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function renderUserInput(userInput: string) { return <div>{escapeHtml(userInput)}</div>; } """ ### 6.4 Dependency Management * **Standard:** Regularly update dependencies to patch security vulnerabilities. * **Why:** Mitigates risks associated with outdated libraries. * **Do This:** Use "npm audit" or "yarn audit" to identify and fix vulnerabilities. ## 7. Testing ### 7.1. Unit Testing * **Standard:** Write unit tests for individual components and utility functions. * **Why:** Ensures code correctness and facilitates refactoring. * **Do This:** """typescript // button.test.tsx import { render, screen } from '@testing-library/react'; import { Button } from './button'; test('renders button with correct text', () => { render(<Button>Click me</Button>); const buttonElement = screen.getByText('Click me'); expect(buttonElement).toBeInTheDocument(); }); """ ### 7.2. Integration Testing * **Standard:** Write integration tests to verify the interaction between components. * **Why:** Ensures that components work together correctly. ### 7.3. End-to-End Testing * **Standard:** Use end-to-end testing frameworks to simulate user interactions and verify the overall application functionality. * **Why:** Validates the entire user flow and catches integration issues. * **Tools:** Cypress, Playwright ## 8. Performance Optimization ### 8.1. Code Splitting * **Standard:** Implement code splitting to reduce the initial load time by loading only the necessary code. * **Why:** Improves application performance, resulting in better user experience, especially with large applications. * **Do This:** * Lazy load with "React.lazy" and "<Suspense>". * Dynamic import statements. ### 8.2. Memoization * **Standard:** Use "React.memo" for functional components that receive the same props repeatedly. Use also "useMemo" and "useCallback" hooks if needed. * **Why:** Avoids unnecessary re-renders. ### 8.3. Image Optimization * **Standard:** Optimize all images by compressing them, using appropriate formats (WebP), and lazy loading them. * **Why:** Reduces bandwidth consumption and improves page load times. * **Do This:** Use the "<Image>" component provided by Next.js. By following these code style and conventions, development teams can create high-quality, maintainable, and performant Shadcn applications. This guide can also be used to configure AI coding assistants for automatic code formatting and style enforcement.
# API Integration Standards for Shadcn This document outlines the standards for integrating APIs with Shadcn-based applications. It covers patterns for connecting with backend services and external APIs, with a focus on maintainability, performance, and security. These standards leverage modern approaches and patterns compatible with the latest versions of Shadcn, React, and related libraries. ## 1. Architecture and Principles ### 1.1. Separation of Concerns **Do This:** * Separate API interaction logic from UI components. Use dedicated services or hooks to handle API calls. **Don't Do This:** * Make direct API calls within Shadcn components. This tightly couples the UI to the backend, making testing and maintenance difficult. **Why**: * *Maintainability*: Decoupled code is easier to understand, test, and modify. * *Reusability*: API logic can be reused across multiple components. * *Testability*: API interactions can be mocked and tested independently. **Example:** """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); return response.data; } catch (error) { console.error("Error fetching products:", error); throw error; // Re-throw to allow components to handle the error } }; // components/ProductList.tsx import { useEffect, useState } from "react"; import { getProducts } from "@/api/productService"; // Assuming "@/api" is aliased in your tsconfig.json import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" function ProductList() { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchProducts = async () => { try { const data = await getProducts(); setProducts(data); setLoading(false); } catch (err: any) { setError(err); setLoading(false); } }; fetchProducts(); }, []); if (loading) { return <div>Loading products...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products.map((product) => ( <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 1.2. Data Fetching Strategies **Do This**: * Use React Query, SWR, or similar libraries for data fetching, caching, and state management. **Don't Do This**: * Rely solely on "useEffect" for API calls and storing data in local component state without any caching strategy. **Why**: * *Performance*: Caching reduces unnecessary API calls. * *User Experience*: Optimistic updates and background data fetching improve responsiveness. * *Simplified State Management*: Reduces boilerplate code for handling loading, error, and data states. **Example (using React Query):** """typescript // api/productService.ts - same as above // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" function ProductList() { const { data: products, isLoading, error } = useQuery({ queryKey: ["products"], queryFn: getProducts, }); if (isLoading) { return <div>Loading products...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products?.map((product) => ( // Safe navigation operator <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 1.3. Error Handling **Do This:** * Implement robust error handling at both the API service level and the component level. * Use try-catch blocks for API calls and provide user-friendly error messages. **Don't Do This:** * Ignore errors or display generic, unhelpful error messages to the user. **Why**: * *User Experience*: Inform users about issues and provide guidance. * *Debugging*: Detailed error messages aid in identifying and resolving problems. * *Resilience*: Gracefully handle unexpected errors to prevent application crashes. **Example**: """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); return response.data; } catch (error: any) { console.error("Error fetching products:", error); //Custom handling based on error type if (error.response && error.response.status === 404) { throw new Error("Products not found."); }else{ throw new Error("Failed to fetch products."); //Generic error } } }; // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { AlertCircle } from "lucide-react" function ProductList() { const { data: products, isLoading, error } = useQuery({ queryKey: ["products"], queryFn: getProducts, }); if (isLoading) { return <div>Loading products...</div>; } if (error) { return ( <Alert variant="destructive"> <AlertCircle className="h-4 w-4" /> <AlertTitle>Error</AlertTitle> <AlertDescription> {error.message} </AlertDescription> </Alert> ); } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products?.map((product) => ( <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 1.4 API Client Setup **Do This:** * Utilize Axios or Fetch API with appropriate headers and base URLs configured. * Implement interceptors for request/response processing (e.g., adding authentication tokens, handling errors). **Don't Do This:** * Use a direct fetch call without error handling or central configuration **Why:** * *Centralized Configuration:* easier to manage base URLs and headers * *Interceptors:* Interceptors provide a mechanism for intercepting and modifying HTTP requests and responses. **Example (Axios):** """typescript // api/apiClient.ts import axios from 'axios'; const apiClient = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); apiClient.interceptors.request.use( (config) => { // Retrieve token from localStorage, cookie, or context const token = localStorage.getItem('authToken'); if (token) { config.headers.Authorization = "Bearer ${token}"; } return config; }, (error) => { return Promise.reject(error); } ); apiClient.interceptors.response.use( (response) => { return response; }, (error) => { // Handle errors, e.g., redirect to login on 401 Unauthorized if (error.response && error.response.status === 401) { // Redirect to login page or handle token refresh window.location.href = '/login'; } return Promise.reject(error); } ); export default apiClient; """ ## 2. Implementation Details ### 2.1. Data Transformation **Do This:** * Transform API responses into a format suitable for the UI. This could involve renaming properties, converting data types, or structuring data for specific components. **Don't Do This:** * Pass raw API responses directly to Shadcn components. Components should receive only the data they need, in the format they expect. **Why**: * *Decoupling*: Protects components from changes in the API response structure. * *Performance*: Reduces the amount of data processed by components. * *Clarity*: Makes component props more predictable and self-documenting. **Example:** """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); //Data transformation const transformedProducts = response.data.map((product: any) => ({ id: product.product_id, name: product.product_name, description: product.product_description, price: product.product_price, imageUrl: product.product_image_url, //rename image URL })); return transformedProducts; } catch (error: any) { console.error("Error fetching products:", error); //Custom handling based on error type if (error.response && error.response.status === 404) { throw new Error("Products not found."); }else{ throw new Error("Failed to fetch products."); //Generic error } } }; """ ### 2.2. Optimistic Updates **Do This:** * Use optimistic updates to provide a more responsive user experience. Optimistically update the UI before the API request completes, and revert the change if the request fails. This is commonly done with React Query's "useMutation". **Don't Do This:** * Wait for the API request to complete before updating the UI, especially for actions like creating or updating data. **Why**: * *User Experience*: Makes the application feel faster and more responsive. * *Perceived Performance*: Immediate feedback improves user satisfaction. **Example (using React Query):** """typescript import { useMutation, useQueryClient } from "@tanstack/react-query"; import { createProduct } from "@/api/productService"; // Assuming this function handles the POST request function ProductForm() { const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: createProduct, onSuccess: () => { // Invalidate the products query to refetch the data queryClient.invalidateQueries({ queryKey: ["products"] }); }, }); const handleSubmit = async (data: any) => { mutation.mutate(data); }; return ( <form onSubmit={handleSubmit}> {/* Form fields */} <button type="submit" disabled={mutation.isLoading}> {mutation.isLoading ? "Creating..." : "Create Product"} </button> {mutation.isError && <div>Error: {mutation.error?.message}</div>} </form> ); } """ ### 2.3. Handling Loading States **Do This:** * Display loading indicators (e.g., spinners, progress bars) while waiting for API responses. **Don't Do This:** * Leave the UI unresponsive or display empty data without indicating that data is being loaded. **Why**: * *User Experience*: Informs users that the application is working and prevents confusion. * *Clarity*: Provides visual feedback about the application's state. **Example (with Shadcn components):** """typescript // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton" function ProductList() { const { data: products, isLoading, error } = useQuery({ queryKey: ["products"], queryFn: getProducts, }); if (isLoading) { return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {Array.from({ length: 3 }).map((_, index) => ( <Card key={index}> <CardHeader> <CardTitle><Skeleton className="h-4 w-[80%]" /></CardTitle> </CardHeader> <CardContent> <Skeleton className="h-4 w-[60%]" /> <Skeleton className="h-4 w-[40%]" /> </CardContent> </Card> ))} </div> ); } if (error) { return <div>Error: {error.message}</div>; } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products?.map((product) => ( <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 2.4. Data Validation **Do This:** * Validate both incoming (request) and outgoing (response) data to ensure data integrity. Libraries like Zod or Yup can be used for schema validation. **Don't Do This:** * Trust that API responses are always in the expected format. **Why**: * *Data Integrity*: Prevents invalid data from being stored or displayed. * *Error Prevention*: Catches errors early, before they cause unexpected behavior. * *Security*: Helps prevent injection attacks and other vulnerabilities. **Example (using Zod):** """typescript import { z } from "zod"; const productSchema = z.object({ id: z.number(), name: z.string(), description: z.string(), price: z.number(), imageUrl: z.string().url(), }); type Product = z.infer<typeof productSchema>; // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); // Validate each item in the array const validatedProducts = z.array(productSchema).parse(response.data); return validatedProducts; } catch (error: any) { console.error("Error fetching products:", error); if (error instanceof z.ZodError) { console.error("Zod validation error:", error.errors); throw new Error("Invalid product data received from the server."); } else if (error.response && error.response.status === 404) { throw new Error("Products not found."); } else { throw new Error("Failed to fetch products."); } } }; """ ### 2.5. Pagination **Do This** Implement server-side pagination for large datasets. Display pagination controls in the UI (e.g., page numbers, next/previous buttons). **Don't Do This** Load an entire dataset into memory at once. **Why** * *Performance*: Reduces the amount of data transferred and processed. * *Scalability*: Enables the application to handle larger datasets. * *User Experience*: Improves page load times. **Example (React Query + Shadcn):** """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async (page: number, limit: number = 10) => { try { const response = await api.get("/products?_page=${page}&_limit=${limit}"); return { data: response.data, totalCount: parseInt(response.headers['x-total-count'] || '0', 10) // Use x-total-count or similar header }; } catch (error: any) { console.error("Error fetching products:", error); throw new Error("Failed to fetch products."); //Generic error } }; // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Button } from "@/components/ui/button" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { useState } from "react" function ProductList() { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const { data, isLoading, error } = useQuery({ queryKey: ["products", page, pageSize], queryFn: () => getProducts(page, pageSize), keepPreviousData: true, //Ensures that the previous data remains visible while the new data is being fetched }); const totalCount = data?.totalCount || 0; const pageCount = Math.ceil(totalCount / pageSize); const handleNextPage = () => { setPage(prev => Math.min(prev + 1, pageCount)); }; const handlePrevPage = () => { setPage(prev => Math.max(prev - 1, 1)); }; const handlePageSizeChange = (size: number) => { setPageSize(size); setPage(1); // Reset to the first page when page size changes }; if (isLoading) { return <div>Loading products...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div> <Table> <TableCaption>A list of your products.</TableCaption> <TableHeader> <TableRow> <TableHead className="w-[100px]">ID</TableHead> <TableHead>Name</TableHead> <TableHead>Description</TableHead> </TableRow> </TableHeader> <TableBody> {data?.data.map((product: any) => ( <TableRow key={product.id}> <TableCell className="font-medium">{product.id}</TableCell> <TableCell>{product.name}</TableCell> <TableCell>{product.description}</TableCell> </TableRow> ))} </TableBody> </Table> <div className="flex justify-between items-center mt-4"> <span>Total Products: {totalCount}</span> <div className="flex items-center space-x-2"> <Select value={pageSize.toString()} onValueChange={(value) => handlePageSizeChange(parseInt(value))}> <SelectTrigger className="w-[180px]"> <SelectValue placeholder="Select page size" /> </SelectTrigger> <SelectContent> <SelectItem value="5">5 per page</SelectItem> <SelectItem value="10">10 per page</SelectItem> <SelectItem value="20">20 per page</SelectItem> </SelectContent> </Select> <Button variant="outline" size="sm" onClick={handlePrevPage} disabled={page === 1}> Previous </Button> <Button variant="outline" size="sm" onClick={handleNextPage} disabled={page === pageCount}> Next </Button> </div> </div> </div> ); } export default ProductList; """ ## 3. Security Considerations ### 3.1. Authentication and Authorization **Do This:** * Implement secure authentication and authorization mechanisms to protect APIs. Use industry-standard protocols like OAuth 2.0 or JWT. * Store API keys and secrets securely (e.g., using environment variables or a secrets management service). * Never commit API keys or secrets directly to the codebase. **Don't Do This:** * Use weak or insecure authentication methods. * Expose API keys or secrets in client-side code. **Why**: * *Data Protection*: Prevents unauthorized access to sensitive data. * *Security*: Reduces the risk of security breaches. * *Compliance*: Adheres to security and privacy regulations. **Example (using JWT for authentication):** """typescript // api/api.ts import axios from 'axios'; const api = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); api.interceptors.request.use( (config) => { const token = localStorage.getItem('jwt'); if (token) { config.headers.Authorization = "Bearer ${token}"; } return config; }, (error) => { return Promise.reject(error); } ); export default api; """ ### 3.2. Input Validation **Do This:** * Validate all user inputs on both the client-side and the server-side to prevent injection attacks and other vulnerabilities. * Use appropriate data types and formats for input fields. * Sanitize user inputs before sending them to the API. **Don't Do This:** * Trust that user inputs are always valid or safe. * Allow users to submit arbitrary code or commands to the API. **Why**: * *Security*: Prevents malicious code from being injected into the application. * *Data Integrity*: Ensures that data is stored in the correct format. * *Reliability*: Prevents unexpected errors caused by invalid data. ### 3.3. Rate Limiting **Do This:** * Implement rate limiting to prevent abuse and protect APIs from being overwhelmed by excessive requests. **Don't Do This:** * Allow unlimited requests from a single user or IP address. **Why**: * *Availability*: Protects APIs from denial-of-service attacks. * *Scalability*: Ensures that APIs can handle a large number of requests. * *Fair Usage*: Prevents abuse of API resources. ## 4. Modern Approaches and Patterns ### 4.1. Server Actions (Next.js 13+) **Do This:** * Utilize Server Actions for server-side data mutations directly from components. This simplifies data handling by eliminating the need for a separate API layer for simple create/update/delete operations. **Don't Do This:** * Overuse Server Actions for complex logic, keep such actions atomic and straightforward. **Why**: * *Simplicity*: Reduces the amount of code needed for data mutations. * *Performance*: Executes data mutations on the server, reducing client-side overhead. * *Security*: Runs server-side code, with no API endpoints being exposed. **Example:** """typescript // app/actions.ts 'use server'; // Mark this file as a server action import { revalidatePath } from 'next/cache'; //Use to revalidate the data export async function createProduct(formData: FormData) { const name = formData.get('name') as string; const description = formData.get('description') as string; // Perform server-side logic here (e.g., database interaction) try { // Simulate database interaction const newProduct = { id: Date.now(), name, description, }; //Revalidate the product list to show new product revalidatePath('/products'); return { success: true, data: newProduct }; } catch (error) { console.error('Error creating product:', error); return { success: false, error: 'Failed to create product' }; } } // app/products/page.tsx import { createProduct } from '../actions'; export default async function ProductsPage() { return ( <form action={createProduct}> <input type="text" name="name" placeholder="Product Name" required/> <input type="text" name="description" placeholder="Description" required/> <button type="submit">Create Product</button> </form> ); } """ ### 4.2. tRPC **Do This:** * Consider using tRPC for building type-safe APIs between your Next.js frontend and backend. This provides end-to-end type safety without requiring code generation steps. **Don't Do This:** * Use tRPC for very large, complex APIs where a more traditional REST or GraphQL approach might be more appropriate. **Why**: * *Type Safety*: Ensures type safety across the entire application. * *Developer Experience*: Simplifies API development with automatic type inference. * *Performance*: Minimizes data serialization and deserialization overhead. This document provides a comprehensive set of guidelines for API integration in Shadcn projects. By adhering to these standards, development teams can ensure that their applications are maintainable, performant, secure, and provide a great user experience.
# Security Best Practices Standards for Shadcn This document outlines the security best practices for developing applications using Shadcn, aiming to help developers build secure, resilient, and maintainable code. These guidelines are essential for protecting against common web vulnerabilities and ensuring the confidentiality, integrity, and availability of your applications. ## 1. Input Validation and Sanitization ### 1.1 The Importance of Input Validation **Why:** Input validation is the cornerstone of web application security. Malicious users can inject harmful data into your application through numerous input fields, leading to vulnerabilities like SQL Injection, Cross-Site Scripting (XSS), and Command Injection. **Do This:** * **Always validate all user inputs:** This includes form fields, URL parameters, cookies, headers, and data from external APIs. * **Use a whitelist approach:** Define acceptable input patterns and reject anything that doesn't match. * **Validate on both client-side and server-side:** Client-side validation enhances user experience, while server-side validation is critical for security. **Don't Do This:** * **Never trust user input implicitly:** Regardless of the source, treat all input as potentially malicious. * **Rely solely on client-side validation:** Bypassing client-side checks is trivial. **Example:** """typescript // Server-side validation with Zod (recommended) import { z } from 'zod'; const userSchema = z.object({ username: z.string().min(3).max(50), email: z.string().email(), age: z.number().min(18).max(120), }); export async function createUser(data: any) { try { const validatedData = userSchema.parse(data); // Process validatedData to your database console.log("validatedData : " , validatedData); return { success: true, data: validatedData }; } catch (error:any) { console.error("Validation Error:", error.errors); return { success: false, error: error.errors }; } } //Usage : const userData = { username: 'john_doe', email: 'john@example.com', age: 25 }; createUser(userData); """ ### 1.2 Sanitizing Output and Data Escaping **Why:** Sanitizing output and escaping data prevents XSS attacks by ensuring that user-provided content is rendered as text, not as executable code. **Do This:** * **Use appropriate escaping mechanisms:** When displaying user input, use the correct escaping methods for the target context (HTML, JavaScript, CSS, URLs, XML, etc.). * **Context-aware encoding:** Choose encoding schemes suitable for where the data will be used. **Don't Do This:** * **Concatenate user input directly into HTML or JavaScript:** Doing so creates opportunities for XSS vulnerabilities. * **Disable escaping features:** Understand the implications and have a robust alternative. **Example:** """tsx //Preventing XSS in React/Shadcn import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { useState } from "react"; function DisplayName({ name }: { name: string }) { const [displayName, setDisplayName] = useState(name) return ( <Card> <CardHeader> <CardTitle>Safe Display of User Name</CardTitle> </CardHeader> <CardContent> <div>Hello, {displayName} !</div> </CardContent> </Card> ); } export default DisplayName; //Usage (Assuming your UI is Typescript-based Shadcn) <DisplayName name={"<script>alert('XSS')</script>"} /> //React escapes by default, correctly rendering the tags as text. //Use "dangerouslySetInnerHTML" with EXTREME CAUTION, only when sanitizing the string """ ### 1.3 Common Anti-Patterns * Ignoring input validation for authenticated users. * Using overly permissive regular expressions for validation. * Trusting hidden form fields and client-side logic. ## 2. Authentication and Authorization ### 2.1 Secure Authentication Practices **Why:** Robust authentication is vital to verify the identity of users accessing your application. Weak authentication schemes can be easily compromised. **Do This:** * **Use strong password policies:** Enforce minimum length, complexity requirements, and encourage password managers. * **Implement multi-factor authentication (MFA):** MFA adds an extra layer of security beyond passwords. * **Use established authentication libraries:** Leverage trusted libraries like NextAuth.js for authentication and session management. * **Rate limit login attempts:** Protect against brute-force attacks. **Don't Do This:** * **Store passwords in plain text:** Always hash passwords using a strong hashing algorithm like bcrypt or Argon2. * **Use predictable session IDs:** Generate cryptographically secure session IDs. * **Implement custom authentication schemes without security expertise:** Employ well-vetted solutions. **Example:** """typescript // Using NextAuth.js for authentication import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { verifyPassword } from "@/lib/auth"; import { getUserByEmail } from "@/lib/db"; export const authOptions = { session: { strategy: "jwt", }, providers: [ CredentialsProvider({ async authorize(credentials) { const email = credentials?.email; const password = credentials?.password; if (!email || !password) { return null; } const user = await getUserByEmail(email); if (!user || !user?.password) { return null; } const isValid = await verifyPassword(password, user.password); if (!isValid) { return null; } return { email: user.email, name: user.name, image: user.image, }; }, }), ], secret: process.env.NEXTAUTH_SECRET, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; """ ### 2.2 Authorization and Access Control **Why:** Authorization defines what authenticated users are allowed to do within your application. Inadequate access controls can lead to privilege escalation attacks. **Do This:** * **Implement role-based access control (RBAC):** Assign permissions based on user roles. * **Use least privilege principle:** Grant users only the minimum necessary permissions to perform their tasks. * **Validate authorization checks on the server-side:** Don't rely solely on client-side checks. * **Centralize authorization logic:** Manage permissions in a consistent and maintainable way. **Don't Do This:** * **Expose sensitive data or functionality without authorization checks.** * **Hardcode user IDs or roles in code.** * **Assume that authenticated users are authorized to access all resources.** **Example:** """typescript //Role-based authorization middleware import { NextRequest, NextResponse } from 'next/server'; import { getSession } from 'next-auth/react'; export async function middleware(req: NextRequest) { const session = await getSession({req}) const url = req.nextUrl.pathname if (!session && url === '/admin') { return NextResponse.redirect(new URL('/login', req.url)) } return NextResponse.next() } export const config = { matcher: ['/admin/:path*'] } """ ### 2.3 Common Anti-Patterns * Defaulting to administrator privileges for all users. * Failing to invalidate sessions after password changes or logout. * Using weak or obsolete cryptographic algorithms. ## 3. Data Protection ### 3.1 Encryption of Sensitive Data **Why:** Encryption protects sensitive data both in transit and at rest. Even if attackers gain access to your database, encrypted data remains confidential. **Do This:** * **Encrypt sensitive data in transit:** Use HTTPS to encrypt all communications between the client and server. * **Encrypt sensitive data at rest:** Use encryption libraries and cloud provider's encryption services to encrypt data stored in databases, file systems, and backups. * **Implement key management practices:** Securely store and manage encryption keys. Utilize Hardware Security Modules (HSMs) or Key Management Services (KMS). * **Rotate keys regularly:** Rotate encryption keys periodically to reduce the impact of potential key compromises. **Don't Do This:** * **Store sensitive data in plain text without encryption.** * **Use weak or outdated encryption algorithms.** * **Hardcode encryption keys in the application code.** **Example:** """typescript //Encryption using a library like 'crypto-js' for demonstration import CryptoJS from 'crypto-js'; const secretKey = process.env.ENCRYPTION_KEY; // Store securely! export function encryptData(data: string): string { if (!secretKey) { throw new Error('Encryption key is not defined.'); } return CryptoJS.AES.encrypt(data, secretKey).toString(); } export function decryptData(encryptedData: string): string { if (!secretKey) { throw new Error('Encryption key is not defined.'); } const bytes = CryptoJS.AES.decrypt(encryptedData, secretKey); return bytes.toString(CryptoJS.enc.Utf8); } """ ### 3.2 Data Minimization and Retention **Why:** Reducing the amount of sensitive data you collect and store minimizes the potential damage from data breaches. **Do This:** * **Collect only necessary data:** Only collect data that is essential for the intended purpose. * **Implement data retention policies:** Define how long data should be stored and securely delete it when no longer needed. * **Anonymize or pseudonymize data:** When possible, remove identifying information or replace it with pseudonyms. **Don't Do This:** * **Collect and store excessive amounts of personal data without a clear purpose.** * **Retain data indefinitely without a retention policy.** * **Fail to securely dispose of data that is no longer needed.** ### 3.3 Common Anti-Patterns * Storing API keys or secrets in public repositories or client-side code. * Failing to implement proper backup and disaster recovery procedures. * Ignoring data privacy regulations (e.g., GDPR, CCPA). ## 4. Dependencies and Third-Party Libraries ### 4.1 Dependency Management **Why:** Third-party libraries and dependencies introduce potential vulnerabilities. Staying up-to-date with patched versions is essential. **Do This:** * **Use a package manager:** Use npm, yarn, or pnpm to manage dependencies. * **Regularly update dependencies:** Use "npm update", "yarn upgrade", or "pnpm update" to keep dependencies updated. * **Automated vulnerability scanning:** Integrate tools like Snyk or Dependabot into your CI/CD pipeline to automatically detect and remediate vulnerabilities. * **Pin exact dependency versions:** Use exact versioning in your "package.json" to avoid unexpected breaking changes. * **Review dependencies:** Understand the dependencies you are using and their potential security risks. **Don't Do This:** * **Use outdated dependencies with known vulnerabilities.** * **Install dependencies from untrusted sources.** * **Ignore security alerts from dependency scanning tools.** **Example:** """json // package.json { "dependencies": { "react": "18.2.0", "zod": "3.22.4", "@radix-ui/react-slot": "1.0.2" }, "devDependencies": { "eslint": "8.56.0", "typescript": "5.3.3" } } """ ### 4.2 Third-Party Component Auditing **Why:** Shadcn UI relies heavily on components; understanding their data handling is critical. **Do This:** * **Review the code of Shadcn UI components:** Familiarize yourself with how data is handled and processed within components. * **Check Radix UI for known vulnerabilities:** Radix UI forms the base of many Shadcn UI components, so stay informed about its security status. * **Follow Shadcn's update recommendations:** Incorporate updates promptly to benefit from security patches and improvements. **Don't Do This:** * **Blindly integrate components without understanding their potential impact on security.** * **Ignore the underlying risks associated with Radix UI.** ### 4.3 Common Anti-Patterns * Using vulnerable or unmaintained third-party libraries. * Ignoring license restrictions on third-party code. * Exposing third-party configuration details. ## 5. Error Handling and Logging ### 5.1 Secure Error Handling **Why:** Error messages can reveal sensitive information about your application. **Do This:** * **Implement generic error messages for users:** Avoid exposing internal details or stack traces. * **Log detailed error information on the server-side:** Log errors to a secure location for debugging and monitoring. * **Use structured logging:** Use a structured logging format (e.g., JSON) to facilitate analysis. * **Handle exceptions gracefully:** Prevent unhandled exceptions from crashing the application or exposing sensitive data. **Don't Do This:** * **Display detailed error messages directly to users.** * **Log sensitive data in error messages.** * **Ignore or suppress errors without proper handling.** **Example:** """typescript // Centralized error handling async function fetchData() { try { // Attempt to fetch data const response = await fetch('https://api.example.com/data'); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); return data; } catch (error: any) { // Log the full error details on the server console.error('Error fetching data:', error); // Return a generic error message to the client. return { error: 'Failed to retrieve data. Please try again later.' }; } } """ ### 5.2 Comprehensive Logging Practices **Why:** Logging provides valuable insights for security monitoring, incident response, and auditing. **Do This:** * **Log important events:** Log authentication attempts, authorization failures, data access, and modifications. * **Include relevant context:** Include timestamps, user IDs, IP addresses, and other relevant information in log entries. * **Secure log storage:** Store logs in a secure location with restricted access. * **Regularly review logs:** Analyze logs for suspicious activity or security incidents. * **Centralized log management:** Use a centralized logging system to collect and analyze logs from multiple sources. **Don't Do This:** * **Log sensitive data in plain text.** * **Store logs on the same server as the application.** * **Fail to monitor logs for security incidents.** ### 5.3 Common Anti-Patterns * Logging too much or too little information. * Writing logs directly to files without proper security controls. * Failing to rotate or archive logs. ## 6. Deployment and Infrastructure Security ### 6.1 Secure Configuration Management **Why:** Vulnerable configurations can expose your application to attacks. **Do This:** * **Use environment variables for configuration:** Store sensitive configuration settings (e.g., API keys, database passwords) in environment variables. * **Automated configuration management:** Use tools like Ansible, Chef, or Puppet to automate configuration management. * **Principle of least privilege for infrastructure access:** Grant only necessary access rights for each component and user. * **Regularly review your environment:** Check environment variables and related configurations (API Keys, database credentials) to prevent leaks. * **Monitor configuration changes:** Track configuration changes to identify and mitigate potential security issues. **Don't Do This:** * **Hardcode configuration settings in code.** * **Store configuration files in public repositories.** * **Grant excessive permissions to infrastructure components.** **Example:** """bash #Example of setting and accessing secure environment variables #On the server : (using .env file) DATABASE_URL=your_db_url API_KEY=your_api_key NEXTAUTH_SECRET=your_nextauth_secret #Accessing it in Next.js/Typescript const apiKey = process.env.API_KEY; if (!apiKey) { console.error('API key is missing from environment variables.'); // Handle the missing API key scenario (e.g., throw an error or use a default value) } """ ### 6.2 Network Security **Why:** Protecting your network infrastructure is crucial to prevent unauthorized access. **Do This:** * **Use firewalls to restrict network traffic:** Configure firewalls to allow only necessary traffic. * **Segment your network:** Isolate different parts of your application (e.g., web server, database server) into separate network segments. * **Regular security audits:** Conduct routine security reviews to identify vulnerabilities. * **Intrusion detection:** Implement intrusion detection systems to monitor for malicious. **Don't Do This:** * **Expose unnecessary ports or services to the internet.** * **Use default passwords for network devices.** * **Fail to monitor network traffic for suspicious activity.** ### 6.3 Common Anti-Patterns * Using default or weak passwords for infrastructure components. * Failing to patch security vulnerabilities in operating systems and software. * Exposing sensitive services to the public internet without proper protection. ## 7. Regular Security Testing and Auditing **Why:** Proactive security testing and auditing can identify vulnerabilities before attackers exploit them. **Do This:** * **Code Reviews:** Perform regular manual code reviews with a focus on security issues. * **Static Analysis:** Use linters, IDE plugins, and static analysis tools to automatically find common security flaws. * **Dynamic Analysis:** Perform dynamic analysis or "fuzzing" to test the application's response to malformed or unexpected inputs. * **Penetration Testing:** Hire security professionals to perform penetration testing – simulating real-world attacks to find weaknesses. * **Security Audits:** Conduct periodic security audits to assess overall security posture. Remember that security is an ongoing process, not a one-time fix. By following these best practices, you can build more secure and resilient Shadcn UI applications.
# Core Architecture Standards for Shadcn This document outlines the core architectural standards for building maintainable, performant, and scalable applications using Shadcn. It provides guidelines for project structure, component organization, data fetching, state management, and other architectural aspects specific to Shadcn. ## 1. Project Structure and Organization A well-defined project structure is crucial for maintainability, collaboration, and scalability. Shadcn, being a UI library that's typically integrated into larger frameworks like Next.js or Remix, benefits significantly from a thoughtfully structured project. ### 1.1. Directory Structure Organize your project using a feature-sliced architecture, grouped by domain and purpose. This promotes modularity and separation of concerns. **Do This:** """ . ├── app/ # Next.js app directory (or equivalent in your framework) │ ├── components/ # Shared, reusable components │ │ ├── ui/ # Shadcn UI components - button, card, etc. │ │ ├── domain/ # Components specific to a business domain (e.g., user profile, product list) │ │ └── utils.ts # Utility functions related to components │ ├── lib/ # Core application logic │ │ ├── utils.ts # General utility functions │ │ ├── api/ # API client and related logic │ │ ├── hooks/ # Custom React hooks │ │ └── services/ # Business logic services │ ├── styles/ # Global styles and theme related files │ │ ├── globals.css # Global CSS file │ │ └── theme.ts # Theme configuration file (using Radix UI themes or similar) │ └── ... ├── public/ # Static assets ├── .env.local # Environment variables ├── tsconfig.json # TypeScript configuration └── ... """ **Don't Do This:** * Flat directory structures * Mixing UI components with business logic within the same directory * Global mutable state **Why This Matters:** * **Maintainability:** Feature-sliced architecture allows for easy modification and extension of specific features without affecting other parts of the application. * **Scalability:** Modular design makes it easier to add new features and scale the application. * **Collaboration:** Clear directory structure improves team collaboration and reduces merge conflicts. ### 1.2. Component Organization Organize components into logical modules based on their functionality and reusability. **Do This:** """ components/ ├── ui/ │ ├── button.tsx │ ├── card.tsx │ └── ... ├── domain/ │ ├── user-profile/ │ │ ├── user-profile.tsx │ │ ├── user-profile.module.css │ │ └── ... │ └── product-list/ │ ├── product-list.tsx │ ├── product-list-item.tsx │ └── ... """ **Don't Do This:** * Large, monolithic components. * Duplicating similar UI logic across multiple components. * Directly embedding complex business logic into UI components. **Why This Matters:** * **Reusability:** Separates UI components from business logic, allowing for easy reuse across different parts of the application. * **Testability:** Smaller, well-defined components are easier to test. * **Readability:** Improves code readability and maintainability. ### 1.3. Shadcn UI Integration When integrating Shadcn UI components, treat them as foundational building blocks and extend or compose them to create higher-level UI elements. **Do This:** """tsx // components/ui/button.tsx (From Shadcn UI) import * as React from "react"; import { cn } from "@/lib/utils"; import { ButtonHTMLAttributes, forwardRef } from "react"; import { VariantProps, cva } from "class-variance-authority"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "bg-transparent border border-input hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "bg-transparent underline-offset-4 hover:underline text-primary", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ); export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {} const Button = forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, ...props }, ref) => { return ( <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} /> ); } ); Button.displayName = "Button"; export { Button, buttonVariants }; // components/domain/user-profile/edit-profile-button.tsx import { Button } from "@/components/ui/button"; export function EditProfileButton() { return ( <Button variant="outline" size="sm"> Edit Profile </Button> ); } """ **Don't Do This:** * Modifying Shadcn UI components directly. Instead, compose them or create wrapper components. * Ignoring the theme and styles provided by Shadcn UI. **Why This Matters:** * **Maintainability:** Upgrading Shadcn UI components becomes easier without conflicts. * **Consistency:** Maintains a consistent look and feel throughout the application. * **Customization:** Allows for easy customization of UI elements while leveraging the base styles and functionalities of Shadcn UI. ## 2. Data Fetching and State Management Efficient data fetching and state management are crucial for application performance and user experience. ### 2.1. Data Fetching Strategies Employ modern data fetching techniques like Server Components or React Server Actions (in Next.js) for optimal initial load performance. Use client-side fetching (e.g., "SWR" or "React Query") only when strictly necessary for interactive updates or mutations. **Do This:** """tsx // app/profile/page.tsx (Next.js Server Component) import { getUserProfile } from "@/lib/api/user"; export default async function ProfilePage() { const user = await getUserProfile(); return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); } // lib/api/user.ts export async function getUserProfile() { // Replace with your actual data fetching logic const res = await fetch('https://api.example.com/user/profile', { headers: { 'Authorization': "Bearer ${process.env.API_TOKEN}" } }); if (!res.ok) { throw new Error('Failed to fetch data') } return res.json() } // app/actions.ts (React Server Action) 'use server' import { revalidatePath } from 'next/cache'; export async function updateProfile(formData: FormData) { // ... Server-side mutation logic // Assuming you have a "updateUserProfile" function in api/user.ts await updateUserProfile(formData) revalidatePath('/profile') // Revalidate the profile page } """ """tsx // Client-Side Fetching Example (using SWR) import useSWR from 'swr' const fetcher = (...args: [string, ...any[]]) => fetch(...args).then(res => res.json()) function Profile() { const { data, error, isLoading } = useSWR('/api/user', fetcher) if (error) return <div>failed to load</div> if (isLoading) return <div>loading...</div> return ( <div> <h1>{data.name}</h1> <p>{data.email}</p> </div> ) } export default Profile """ **Don't Do This:** * Fetching data directly in client-side components for initial page load, if server-side fetching is possible. * Over-fetching data. * Ignoring caching mechanisms for frequently accessed data. **Why This Matters:** * **Performance:** Server-side rendering and static site generation provide faster initial load times. * **SEO:** Server-rendered content is easily crawled by search engines. * **User Experience:** Reduces perceived latency and improves overall app responsiveness. ### 2.2. State Management Choose a state management solution that aligns with the complexity of your application. For simple component-level state, use "useState" and "useReducer". For more complex application-wide state, consider using Context API with a reducer, or a dedicated state management library like Zustand or Jotai. Avoid Redux unless the application has extremely complex state requirements. **Do This:** """tsx // Simple component state (useState) import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } // Context API with useReducer (application-wide state) import React, { createContext, useReducer, useContext } from 'react'; type State = { theme: 'light' | 'dark'; }; type Action = | { type: 'SET_THEME'; payload: 'light' | 'dark' }; const initialState: State = { theme: 'light', }; const ThemeContext = createContext<{ state: State; dispatch: React.Dispatch<Action>; }>({ state: initialState, dispatch: () => null, }); const themeReducer = (state: State, action: Action): State => { switch (action.type) { case 'SET_THEME': return { ...state, theme: action.payload }; default: return state; } }; const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [state, dispatch] = useReducer(themeReducer, initialState); return ( <ThemeContext.Provider value={{ state, dispatch }}> {children} </ThemeContext.Provider> ); }; const useTheme = () => { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; }; export { ThemeProvider, useTheme }; """ """tsx // Zustand example import { create } from 'zustand' interface BearState { bears: number increase: (by: number) => void } const useBearStore = create<BearState>()((set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), })) function BearCounter() { const bears = useBearStore((state) => state.bears) const increase = useBearStore((state) => state.increase) return ( <> <h1>{bears} Bears in the store</h1> <button onClick={() => increase(2)}>Increase bears</button> </> ) } """ **Don't Do This:** * Overusing global state for local component state. * Mutating state directly. * Using Redux for simple state management scenarios. **Why This Matters:** * **Predictability:** Makes state changes predictable and easier to debug. * **Performance:** Prevents unnecessary re-renders by only updating components that depend on the changed state. * **Maintainability:** Centralized state management improves codebase organization and makes it easier to reason about application state. ### 2.3. Optimistic Updates When performing mutations, consider using optimistic updates to provide immediate feedback to the user while the request is processing in the background. **Do This:** """tsx // Example using SWR's mutate function for optimistic updates import useSWR, { mutate } from 'swr' function LikeButton({ postId }) { const { data: post } = useSWR("/api/posts/${postId}") const handleLike = async () => { // Optimistically update the UI mutate("/api/posts/${postId}", { ...post, likes: post.likes + 1 }, { revalidate: false }) try { // Send the like request to the server await fetch("/api/posts/${postId}/like", { method: 'POST' }) // Revalidate the data to ensure it's up-to-date mutate("/api/posts/${postId}") } catch (error) { // If the request fails, revert the optimistic update mutate("/api/posts/${postId}", post, { revalidate: false }) console.error('Failed to like post', error) // Optionally, show an error message to the user } } return ( <button onClick={handleLike} disabled={isLoading}> Like ({post?.likes || 0}) </button> ) } """ **Don't Do This:** * Ignoring potential errors during optimistic updates and failing to revert the UI. * Optimistically updating state that depends on server-side validation. **Why This Matters:** * **User Experience:** Provides a more responsive and engaging user experience. * **Perceived Performance:** Makes the application feel faster. ## 3. Code Quality and Best Practices Maintaining high code quality is essential for long-term maintainability, collaboration, and reducing the risk of bugs. ### 3.1. TypeScript Usage Embrace TypeScript for type safety and improved code maintainability. **Do This:** * Use explicit types for function parameters, return values, and variables. * Define custom types and interfaces to represent data structures. * Enable strict mode in "tsconfig.json" for stricter type checking. * Leverage TypeScript's features like generics, enums, and discriminated unions to write more robust and reusable code. """tsx interface User { id: string; name: string; email: string; } async function fetchUser(id: string): Promise<User> { const response = await fetch("/api/users/${id}"); const user: User = await response.json(); return user; } """ **Don't Do This:** * Using "any" type excessively. * Ignoring TypeScript errors and warnings. * Writing code that bypasses type checking. **Why This Matters:** * **Type Safety:** Prevents runtime errors by catching type-related issues during development. * **Maintainability:** Makes code easier to understand, refactor, and maintain. * **Collaboration:** Improves team collaboration by providing clear and consistent type information. ### 3.2. Code Formatting and Linting Use a code formatter (e.g., Prettier) and linter (e.g., ESLint) to enforce consistent code style and identify potential issues. **Do This:** * Configure Prettier and ESLint with opinionated rules for code formatting and linting. * Integrate Prettier and ESLint into your development workflow (e.g., using VS Code extensions or pre-commit hooks). * Follow the configured rules consistently. **Don't Do This:** * Ignoring formatting and linting errors. * Disabling important linting rules. * Committing code that doesn't adhere to the established code style. **Why This Matters:** * **Consistency:** Ensures a consistent code style throughout the project. * **Readability:** Improves code readability and maintainability. * **Error Prevention:** Identifies potential issues and helps prevent bugs. ### 3.3. Error Handling Implement robust error handling to gracefully handle unexpected errors and prevent application crashes. **Do This:** * Use "try...catch" blocks to catch potential errors in asynchronous operations. * Provide informative error messages to the user. * Log errors to a centralized logging service for debugging and monitoring. * Use error boundaries to prevent errors in one component from crashing the entire application. """tsx async function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); return data; } catch (error) { console.error("Failed to fetch data:", error); // Optionally, display an error message to the user return null; } } """ **Don't Do This:** * Ignoring errors or suppressing them silently. * Displaying technical error messages to the user. * Allowing errors to crash the application. **Why This Matters:** * **Stability:** Prevents application crashes and ensures a more stable user experience. * **Debuggability:** Makes it easier to identify and fix errors. * **User Experience:** Provides a better user experience by gracefully handling errors and providing informative messages. ### 3.4. Accessibility (A11y) Prioritize accessibility to ensure that your application is usable by everyone, including users with disabilities. **Do This:** * Use semantic HTML elements. * Provide alternative text for images. * Ensure sufficient color contrast. * Make sure all interactive elements are keyboard accessible. * Use ARIA attributes to provide additional context to assistive technologies. **Don't Do This:** * Ignoring accessibility guidelines. * Using non-semantic HTML elements for structure. * Creating keyboard traps. **Why This Matters:** * **Inclusivity:** Makes the application usable by a wider audience. * **Ethical Responsibility:** It's the right thing to do. * **SEO:** Accessible websites tend to rank higher in search results. * **Legal Compliance:** Many regions have laws mandating accessibility. ### 3.5. Comments and Documentation Write clear and concise comments to explain complex logic, document components, and provide context. Use JSDoc or TypeScript-style documentation comments to generate API documentation. **Do This:** """tsx /** * Fetches user data from the API. * * @param {string} id - The ID of the user to fetch. * @returns {Promise<User>} The user data. * @throws {Error} If the API request fails. */ async function fetchUser(id: string): Promise<User> { // Implement data fetching logic here } """ **Don't Do This:** * Writing obvious or redundant comments. * Leaving outdated or incorrect comments. * Neglecting to document complex code. **Why This Matters:** * **Maintainability:** Improves code readability and makes it easier for others (and yourself in the future) to understand the code. * **Collaboration:** Facilitates team collaboration by providing clear explanations of code logic. * **Knowledge Sharing:** Preserves knowledge about the codebase.