# Tooling and Ecosystem Standards for Shadcn
This document outlines the recommended tooling and ecosystem standards for developing applications using Shadcn UI. Adhering to these standards ensures consistency, maintainability, and optimal performance across the project. This document will focus on tools and libraries that either directly enhance the Shadcn UI experience or are commonly used within the Shadcn ecosystem to create a cohesive development flow.
## 1. Core Tooling Recommendations
This section outlines the essential tooling recommendations for a Shadcn UI project.
### 1.1. Package Manager
**Standard:** Use "pnpm" as the package manager.
**Why:** "pnpm" is significantly faster and more efficient than "npm" or "yarn" due to its use of symlinks and a content-addressable file system. This results in faster installation times and reduced disk space usage. This is especially beneficial in monorepo setups.
**Do This:**
"""bash
# Install pnpm
npm install -g pnpm
"""
**Don't Do This:** Use "npm" or "yarn" without a specific, well-documented reason.
### 1.2. TypeScript
**Standard:** Utilize TypeScript for all components and application logic.
**Why:** TypeScript facilitates static typing, reducing runtime errors and improving code maintainability and scalability. Shadcn UI is built with TypeScript, making integration seamless.
**Do This:** Ensure strict TypeScript configurations in "tsconfig.json".
"""json
// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
"""
**Don't Do This:** Use JavaScript without a compelling need.
### 1.3. Linting and Formatting
**Standard:** Employ ESLint with Prettier for consistent code formatting and linting.
**Why:** Consistent code style enhances readability and reduces cognitive load. ESLint helps identify potential errors early in the development process. Prettier automates code formatting.
**Do This:** Install and configure ESLint and Prettier with recommended plugins.
"""bash
pnpm add -D eslint prettier eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y @typescript-eslint/eslint-plugin @typescript-eslint/parser
"""
**Do this:** Add ".eslintrc.js"
"""javascript
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
plugins: ['react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'],
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'no-unused-vars': 'warn',
},
settings: {
react: {
version: 'detect',
},
},
};
"""
**Do this:** Add ".prettierrc.js"
"""javascript
module.exports = {
semi: false,
trailingComma: 'all',
singleQuote: true,
printWidth: 120,
tabWidth: 2,
};
"""
**Don't Do This:** Ignore linting errors or inconsistencies in formatting.
### 1.4. Component Library (Beyond Shadcn UI)
**Standard:** Prefer fully styled Shadcn components or build upon them. If complex components are needed, evaluate headless UI libraries like Radix UI.
**Why:** Shadcn provides a set of unstyled components that must be styled to match the application's needs. Fully styled Shadcn compoennts can provide consistency and speed up development. Radix UI offers great composability and accessibility.
**Do This:**
"""jsx
// Example: Styling a Shadcn Button component with Tailwind CSS
import { Button } from "@/components/ui/button"
function MyButton() {
return (
Click me
)
}
"""
**Do This:** Use Radix UI with React Aria for complex UI components that are fully accessible and composable.
"""jsx
// Example: Using Radix UI with React Aria to define accessibility hooks
import { Button } from '@radix-ui/react-button';
import { useButton } from 'react-aria';
function MyAccessibleButton({ children, ...props }) {
let ref = React.useRef(null);
let { buttonProps } = useButton(props, ref);
return (
{children}
);
}
export default MyAccessibleButton;
"""
**Don't Do This:** Introduce multiple different component libraries in order to keep styling and performance consistent.
## 2. Shadcn-Specific Tooling
This section focuses on tools that directly operate with Shadcn UI.
### 2.1. Shadcn UI CLI
**Standard:** Use the Shadcn UI CLI ("shadcn-ui/cli") to add and manage components.
**Why:** The CLI automates the component installation process, ensuring correct dependencies and project configuration.
**Do This:** Install the CLI globally.
"""bash
pnpm add -g shadcn-ui/cli
"""
**Do This:** Use the CLI to add components.
"""bash
shadcn-ui add button
"""
**Don't Do This:** Manually copy and paste component code without using the CLI, as it can lead to dependency issues.
### 2.2. Tailwind CSS
**Standard:** Utilize Tailwind CSS for styling Shadcn UI components.
**Why:** Shadcn UI is designed to work seamlessly with Tailwind CSS, offering extensive customization options.
**Do This:** Configure Tailwind CSS with a "tailwind.config.js" file.
"""javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
}
"""
**Do This:** Use Tailwind CSS utility classes for styling.
"""jsx
Click me
"""
**Don't Do This:** Use inline styles or custom CSS without a clear justification to maintain consistency.
### 2.3. Radix UI Colors
**Standard:** Use Radix UI Colors for consistent and accessible color palettes.
**Why:** Radix UI Colors provides a comprehensive set of color scales designed for UI development, ensuring good contrast ratios and accessibility.
**Do This:** Install Radix UI Colors.
"""bash
pnpm add @radix-ui/colors
"""
**Do This:** Refer to Radix UI Colors in your Tailwind CSS configuration.
"""javascript
const colors = require('tailwindcss/colors')
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
gray: colors.zinc, // Use zinc instead of gray
primary: colors.blue,
secondary: colors.green,
},
},
},
plugins: [],
}
"""
**Don't Do This:** Use arbitrary color values without considering accessibility and consistency.
### 2.4. Theme Provider
**Standard:** Implement a Theme Provider to toggle between light and dark modes. Libraries like "next-themes" are ideal.
**Why:** Users often prefer a dark mode to reduce eye strain, and a theme provider allows easy switching between themes.
**Do This:** Install "next-themes".
"""bash
pnpm add next-themes
"""
**Do This:** Configure the Theme Provider in "_app.tsx".
"""jsx
import { ThemeProvider } from 'next-themes'
function MyApp({ Component, pageProps }) {
return (
)
}
export default MyApp
"""
**Do This:** Use the "useTheme" hook to toggle themes.
"""jsx
import { useTheme } from 'next-themes'
function ThemeSwitcher() {
const { theme, setTheme } = useTheme()
return (
setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
)
}
"""
**Don't Do This:** Implement theme switching manually without using a dedicated library.
## 3. Development Workflow Tools
### 3.1. Git and Version Control
**Standard:** Use Git for version control and GitHub/GitLab/Bitbucket for repository hosting.
**Why:** Version control is crucial for tracking changes, collaborating with team members, and reverting to previous states if necessary. Following Gitflow or a similar branching strategy makes it simple to manage releases.
**Do This:**
* Commit frequently with descriptive messages.
* Use feature branches for new features and bug fixes.
* Use pull requests for code review.
**Don't Do This:**
* Commit directly to the main branch without review.
* Ignore ".gitignore" rules (node_modules, .env, etc.).
### 3.2. VS Code Extensions
**Standard:** Use VS Code with recommended extensions to improve developer experience.
**Why:** VS Code is a versatile code editor with a vast ecosystem of extensions that improve development speed and code quality.
**Do This:**
* **ESLint:** Integrates ESLint for real-time linting.
* **Prettier:** Formats code on save.
* **Tailwind CSS IntelliSense:** Provides autocompletion and syntax highlighting for Tailwind CSS.
* **GitHub Copilot:** Provides AI-powered code completion.
* **TypeScript Hero:** Manages TypeScript project settings.
**Don't Do This:** Rely solely on default VS Code settings without leveraging extensions to enhance productivity.
### 3.3. Debugging Tools
**Standard:** Use browser developer tools and VS Code's debugging capabilities.
**Why:** Efficient debugging saves time and helps identify issues quickly.
**Do This:**
* Use "console.log" judiciously for debugging simple issues.
* Use VS Code's debugger for more complex issues.
* Use React DevTools for inspecting component props and state.
**Don't Do This:** Rely solely on trial and error for debugging.
### 3.4. Testing Framework
**Standard:** Use Jest with React Testing Library for unit and integration testing.
**Why:** Testing ensures code reliability and helps prevent regressions.
**Do This:**
* Write unit tests for individual components and functions.
* Write integration tests to ensure components work together correctly.
* Use React Testing Library to simulate user interactions.
**Don't Do This:** Skip writing tests or write superficial tests that don't cover critical functionality.
### 3.5. Storybook
**Standard:** Use Storybook for developing and showcasing components in isolation.
**Why:** Storybook enables a more focused approach to component development and faciliates reusable components, improving team collaboration.
**Do This:**
* Create stories for all reusable components.
* Use Storybook to visualize different component states and props.
* Add documentation to stories to describe component usage.
**Don't Do This:** Avoid using Storybook for complex logic or business processes.
## 4. Performance and Optimization
### 4.1. Code Splitting
**Standard:** Implement code splitting to reduce initial load time.
**Why:** Code splitting reduces the size of the initial JavaScript bundle, resulting in faster page load times.
**Do This:**
* Use dynamic imports for splitting code into smaller chunks.
* Use React.lazy and Suspense for lazy-loading components.
* Use Next.js dynamic imports.
**Don't Do This:** Load all code upfront without considering the impact on performance.
### 4.2. Image Optimization
**Standard:** Optimize images to reduce file size and improve loading speed.
**Why:** Large images slow down page load times and consume bandwidth unnecessarily.
**Do This:**
* Use optimized image formats (WebP, AVIF).
* Use responsive images with different sizes for different devices.
* Use a Content Delivery Network (CDN) for serving images.
**Don't Do This:** Use large, unoptimized images without compression or resizing.
### 4.3. Memoization
**Standard:** Use memoization techniques to prevent unnecessary re-renders.
**Why:** Memoization avoids re-renders of components that haven't changed, improving performance, particularly with complex rendering logic or frequent data updates.
**Do This:**
* Use "React.memo" to memoize functional components.
* Use "useMemo" and "useCallback" hooks to memoize values and functions.
**Don't Do This:** Overuse memoization, as it can add unnecessary complexity.
### 4.4. Performance Monitoring
**Standard:** Use tools like Lighthouse and browser developer tools to monitor and optimize performance.
**Why:** Monitoring performance helps identify bottlenecks and areas for improvement.
**Do This:**
* Run Lighthouse audits to identify performance issues.
* Use browser developer tools to analyze network requests and rendering performance.
**Don't Do This:** Ignore performance metrics and rely solely on gut feelings.
### 4.5 Server Side Rendering and Static Site Generation
**Standard:** Use Server Side Rendering (SSR) or Static Site Generation (SSG) where applicable to boost initial load performance and SEO.
**Why:** SSR renders the initial HTML on the server, providing faster load times and improved SEO compared to client-side rendering Only. SSG pre-renders pages at build time.
**Do This:**
"""jsx
// Use SSR
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
// Use SSG
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
"""
**Don't Do This:** Use client-side rendering without considering the performance impact.
## 5. Security
### 5.1. Dependency Management
**Standard:** Keep dependencies up to date and monitor for security vulnerabilities use tools like "npm audit" or "pnpm audit".
**Why:** Outdated dependencies may contain security vulnerabilities that can be exploited by attackers.
**Do This:**
* Run "pnpm outdated" regurlarly to check package versions
* Use "pnpm audit" to identify and address security vulnerabilities.
* Use Dependabot or similar tools to automate dependency updates.
**Don't Do This:** Ignore dependency updates or security warnings.
### 5.2. Input Validation
**Standard:** Validate all user inputs to prevent injection attacks.
**Why:** Input validation ensures that user inputs conform to expected formats, preventing malicious code from being injected into the application.
**Do This:**
* Use server-side validation to verify inputs after they are submitted.
* Use libraries like Zod or Yup for schema validation.
**Don't Do This:** Trust user inputs without validation.
### 5.3. Authentication and Authorization
**Standard:** Implement secure authentication and authorization mechanisms. Use libraries like NextAuth.js
**Why:** Secure authentication and authorization protect sensitive data and prevent unauthorized access. Incomplete auth will expose data to malicious actors.
**Do This:**
* Use strong password hashing algorithms, using libraries like bcryptjs.
* Implement multi-factor authentication.
* Use JWTs for authentication of API calls.
**Don't Do This:** Store passwords in plaintext or use weak authentication methods.
### 5.4. Cross-Site Scripting (XSS) Protection
**Standard:** Sanitize user inputs to prevent XSS attacks.
**Why:** XSS attacks can allow attackers to inject malicious scripts into the application, compromising user data.
**Do This:**
* Use libraries like DOMPurify.
**Don't Do This:** Render user-generated content without proper sanitization.
These standards provide a firm foundation for building reliable, maintainable, and scalable Shadcn UI applications. Following them will facilitate consistent and high-quality code, enhance team collaboration, and minimize potential problems.
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'
# Component Design Standards for Shadcn This document outlines the coding standards for component design when working with Shadcn. Following these guidelines will ensure that your components are reusable, maintainable, performant, and secure. ## 1. Component Structure and Organization ### 1.1. File and Folder Structure **Do This:** * Organize components into a clear, logical directory structure. A common pattern is "components/<ComponentName>/<ComponentName>.tsx" for the main component file, and "components/<ComponentName>/index.ts" for exporting. * Keep related files (styles, tests, stories) within the same component directory. **Don't Do This:** * Dump all components into a single "components" directory. * Mix different types of files (components, styles, tests) within the same directory without clear organization. **Why:** A well-structured file system improves discoverability, maintainability, and collaboration. It facilitates easier navigation and reduces the likelihood of naming conflicts. **Example:** """ src/ └── components/ ├── Button/ │ ├── Button.tsx │ ├── Button.module.css │ ├── Button.stories.tsx │ ├── Button.test.tsx │ └── index.ts ├── Card/ │ ├── Card.tsx │ ├── CardHeader.tsx │ ├── CardBody.tsx │ ├── CardFooter.tsx │ └── index.ts └── index.ts // Exports from all components """ ### 1.2. Single Responsibility Principle **Do This:** * Each component should have a single, well-defined purpose. If a component becomes too complex, refactor it into smaller, more focused components. **Don't Do This:** * Create "god components" that handle multiple unrelated responsibilities. * Write components that are tightly coupled to specific use cases. **Why:** The Single Responsibility Principle makes components easier to understand, test, and reuse. It reduces the impact of changes, as modifications to one component are less likely to affect others. **Example:** Instead of a single "UserProfile" component that fetches data, formats it, and displays it, create separate components for: * "UserProfileData": Fetches the user data. * "UserProfileFormatter": Formats the data (e.g., date formatting). * "UserProfileDisplay": Displays the formatted data. """tsx // UserProfileData.tsx interface UserProfile { name: string; email: string; createdAt: Date; } async function fetchUserProfile(userId: string): Promise<UserProfile> { // Simulate fetching data return new Promise((resolve) => { setTimeout(() => { resolve({ name: 'John Doe', email: 'john.doe@example.com', createdAt: new Date() }); }, 500); }); } export function UserProfileData({ userId }: { userId: string }) { const [profile, setProfile] = React.useState<UserProfile | null>(null); React.useEffect(() => { fetchUserProfile(userId).then(setProfile); }, [userId]); if (!profile) { return <div>Loading...</div>; } return <UserProfileFormatter profile={profile} />; } // UserProfileFormatter.tsx import { format } from 'date-fns'; interface UserProfile { name: string; email: string; createdAt: Date; } interface FormattedUserProfile { name: string; email: string; createdAt: string; } function formatUserProfile(profile: UserProfile): FormattedUserProfile { return { name: profile.name, email: profile.email, createdAt: format(profile.createdAt, 'MMMM dd, yyyy'), }; } export function UserProfileFormatter({ profile }: { profile: UserProfile }) { const formattedProfile = formatUserProfile(profile); return <UserProfileDisplay profile={formattedProfile} />; } // UserProfileDisplay.tsx interface UserProfile { name: string; email: string; createdAt: string; } export function UserProfileDisplay({ profile }: { profile: UserProfile }) { return ( <div> <h2>{profile.name}</h2> <p>Email: {profile.email}</p> <p>Created At: {profile.createdAt}</p> </div> ); } //Usage <UserProfileData userId="123" /> """ ### 1.3. Controlled vs. Uncontrolled Components **Do This:** * Prefer controlled components when you need to manage the component's state from the parent. * Use uncontrolled components when you want the component to manage its own state internally. * Provide mechanisms for the parent to influence the uncontrolled component's state (e.g., through callbacks). **Don't Do This:** * Mix controlled and uncontrolled patterns within the same component without a clear reason. * Overuse controlled components when uncontrolled components would be simpler and more appropriate. **Why:** Properly choosing between controlled and uncontrolled components improves flexibility and maintainability. Controlled components provide fine-grained control, while uncontrolled components simplify common use cases. **Example (Controlled Component):** """tsx // ControlledInput.tsx import React from 'react'; interface ControlledInputProps { value: string; onChange: (newValue: string) => void; } export function ControlledInput({ value, onChange }: ControlledInputProps) { return ( <input type="text" value={value} onChange={(e) => onChange(e.target.value)} /> ); } // Usage function ParentComponent() { const [inputValue, setInputValue] = React.useState(''); return ( <div> <ControlledInput value={inputValue} onChange={(newValue) => setInputValue(newValue)} /> <p>Value: {inputValue}</p> </div> ); } """ **Example (Uncontrolled Component):** """tsx // UncontrolledInput.tsx import React from 'react'; interface UncontrolledInputProps { defaultValue?: string; onChange?: (newValue: string) => void; } export function UncontrolledInput({ defaultValue = "", onChange }: UncontrolledInputProps) { const [value, setValue] = React.useState(defaultValue); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setValue(e.target.value); onChange?.(e.target.value); }; return <input type="text" defaultValue={defaultValue} onChange={handleChange} />; } // Usage function ParentComponent() { const handleInputChange = (newValue: string) => { console.log('Input changed:', newValue); }; return ( <div> <UncontrolledInput defaultValue="Initial Value" onChange={handleInputChange} /> </div> ); } """ ## 2. Styling and Theming with Shadcn ### 2.1. Utilizing "cn" Utility **Do This:** * Use the "cn" utility (from "tailwind-merge" and "clsx") to conditionally apply Tailwind CSS classes. "cn" has now become a part of most Shadcn UI packages so install with "npm install tailwind-merge clsx". **Don't Do This:** * Use string concatenation to build class names, as it's error-prone and less efficient. * Manually handle class name conflicts. **Why:** The "cn" utility simplifies class name management, ensures consistent styling, and improves performance by avoiding unnecessary re-renders. It also prevents class conflicts by merging Tailwind CSS classes intelligently. **Example:** """tsx import { cn } from "@/lib/utils" // or wherever you've placed your utils.ts interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { variant?: "primary" | "secondary"; size?: "small" | "medium" | "large"; } export function Button({ variant = "primary", size = "medium", className, ...props }: ButtonProps) { return ( <button className={cn( "rounded-md font-semibold", "ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "transition-colors duration-200", variant === "primary" && "bg-primary text-primary-foreground hover:bg-primary/80", variant === "secondary" && "bg-secondary text-secondary-foreground hover:bg-secondary/80", size === "small" && "px-2 py-1 text-sm", size === "medium" && "px-4 py-2 text-base", size === "large" && "px-6 py-3 text-lg", className )} {...props} > {props.children} </button> ); } """ Here we're using the "cn" utlity to conditionally set multiple tailwind classes depending on which props the component is passed. ### 2.2. Theme Variables **Do This:** * Use CSS variables (custom properties) for theming. Define variables for colors, fonts, spacing, and other theme-related values. * Organize theme variables in a dedicated file (e.g., "src/styles/theme.css" or "app/theme.ts") * Shadcn provides a "theme.json" that can be customized: "npx shadcn-ui@latest init" **Don't Do This:** * Hardcode theme values directly in component styles. * Use magic numbers for spacing or sizing. **Why:** CSS variables allow for easy theme customization and consistency across the application. For example, using "themes.json" for component customizations ensures consistency in style. By defining variables, you can quickly update the look and feel of your application without modifying individual components. **Example:** """css /* src/styles/theme.css */ :root { --primary-color: #007bff; --secondary-color: #6c757d; --font-size-base: 16px; --spacing-unit: 8px; } /* Component Style */ .my-component { color: var(--primary-color); font-size: var(--font-size-base); padding: var(--spacing-unit) * 2; } """ ### 2.3. Component Variants with Tailwind CSS **Do This:** * Leverage Tailwind CSS's utility-first approach to create component variants. * Use class composition to define different styles based on component props. * Use the "cn" utility to organize the setting of styles of component varients. **Don't Do This:** * Create overly complex or deeply nested Tailwind CSS classes. * Modify Tailwind CSS's core configuration unless absolutely necessary. **Why:** Tailwind CSS allows for creating highly customizable components with ease. Through a utility-first methodology this cuts down on verbose custom CSS files. **Example:** """tsx import { cn } from "@/lib/utils" type ButtonVariant = "primary" | "secondary" | "ghost" type ButtonSize = "sm" | "md" | "lg" interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { variant?: ButtonVariant size?: ButtonSize children?: React.ReactNode } export function Button({ variant = "primary", size = "md", className, children, ...props }: ButtonProps) { return ( <button className={cn( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none", variant === "primary" && "bg-primary text-primary-foreground hover:bg-primary/90", variant === "secondary" && "bg-secondary text-secondary-foreground hover:bg-secondary/80", variant === "ghost" && "hover:bg-accent hover:text-accent-foreground", size === "sm" && "px-2 py-1", size === "md" && "px-4 py-2", size === "lg" && "px-6 py-3 text-lg font-semibold", className )} {...props} > {children} </button> ) } """ ## 3. Component Logic and State Management ### 3.1. Hooks for Reusable Logic **Do This:** * Extract complex logic into custom hooks to promote reuse and separation of concerns. * Name hooks using the "use" prefix (e.g., "useFetchData", "useFormValidation"). * Ensure hooks are composable and can be used in multiple components. **Don't Do This:** * Embed complex logic directly within components, making them harder to understand and test. * Create hooks that are tightly coupled to specific components or contexts. **Why:** Hooks allow you to reuse stateful logic between components. They improve code organization, testability, and maintainability. **Example:** """tsx // useFetchData.ts import { useState, useEffect } from 'react'; interface FetchResult<T> { data: T | null; loading: boolean; error: Error | null; } function useFetchData<T>(url: string): FetchResult<T> { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState<boolean>(true); const [error, setError] = useState<Error | null>(null); useEffect(() => { const fetchData = async () => { try { const response = await fetch(url); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const result: T = await response.json(); setData(result); } catch (e: any) { setError(e); } finally { setLoading(false); } }; fetchData(); }, [url]); // URL is the dependancy. If the url changes, the effect will re-run return { data, loading, error }; } export default useFetchData; // MyComponent.tsx import useFetchData from './useFetchData'; interface DataItem { id: number; name: string; } function MyComponent() { const { data, loading, error } = useFetchData<DataItem[]>('https://api.example.com/data'); if (loading) { return <div>Loading...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <ul> {data?.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> ); } """ ### 3.2. Context for Global State **Do This:** * Use React Context for managing global state that needs to be accessed by multiple components. * Create provider components to wrap the application and make the state available to all descendants. * Use "useContext" hook to consume the context value in the components. **Don't Do This:** * Overuse context for local component state. * Mutate context values directly without using a state management solution (e.g., "useState" or "useReducer"). **Why:** Context provides a way to pass data through the component tree without having to pass props down manually at every level. This simplifies component composition and reduces prop drilling. **Example:** """tsx // ThemeContext.tsx import { createContext, useContext, useState, ReactNode } from 'react'; type Theme = 'light' | 'dark'; interface ThemeContextProps { theme: Theme; toggleTheme: () => void; } const ThemeContext = createContext<ThemeContextProps>({ theme: 'light', // default value toggleTheme: () => {}, // no-op function }); interface ThemeProviderProps { children: ReactNode; } export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => { const [theme, setTheme] = useState<Theme>('light'); const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); }; export const useTheme = (): ThemeContextProps => { const context = useContext(ThemeContext); if (!context) { throw new Error("useTheme must be used within a ThemeProvider"); } return context; }; // MyComponent.tsx import { useTheme } from './ThemeContext'; function MyComponent() { const { theme, toggleTheme } = useTheme(); return ( <div> <p>Current theme: {theme}</p> <button onClick={toggleTheme}>Toggle Theme</button> </div> ); } // App.tsx import { ThemeProvider } from './ThemeContext'; import MyComponent from './MyComponent'; function App() { return ( <ThemeProvider> <MyComponent /> </ThemeProvider> ); } """ ### 3.3. State Management Libraries **Do This:** * Consider using state management libraries like Zustand, Jotai, or Redux for complex applications with advanced state requirements. * Choose a library that aligns with your project needs and team expertise. * Follow the library's recommended patterns and best practices. **Don't Do This:** * Introduce a state management library prematurely, before assessing the need for it. * Overuse global state when component-level state would suffice. **Why:** State management libraries provide structured ways to manage application state, especially when dealing with complex data flows, asynchronous actions, and shared state across multiple components. ### 3.4. Using Shadcn UI React Hooks **Do This:** * Properly utilize the available react hooks that are provided by Shadcn for forms and controlled components. Shadcn uses the "useForm" hook to manage form state, validation, and submission. **Don't Do This:** * Recreate or rewrite custom logic to handle form submissions and validation if a proper hook exists. **Why:** Shadcn UI react hooks provide an out-of-the-box way to perform common and complex tasks that are often seen in modern web development. Using hooks from Shadcn promotes standardization across the project which in turn reduces debugging. **Example** """tsx import { useForm } from "react-hook-form" type FormValues = { username: string } function MyForm() { const form = useForm<FormValues>({ defaultValues: { username: "", }, }) function onSubmit(values: FormValues) { console.log(values) } return ( <form onSubmit={form.handleSubmit(onSubmit)}> <input type="text" {...form.register("username")} /> <button type="submit">Submit</button> </form> ) } """ ## 4. Accessibility (A11y) ### 4.1. Semantic HTML **Do This:** * Use semantic HTML elements to provide structure and meaning to your components. * Use appropriate ARIA attributes when semantic HTML is not sufficient. **Don't Do This:** * Use generic "div" and "span" elements excessively without providing semantic context. * Rely solely on CSS to convey meaning or structure. **Why:** Semantic HTML improves accessibility for users with disabilities and enhances SEO. It allows assistive technologies to understand the content and provide appropriate feedback. **Example:** """tsx // Good <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> </ul> </nav> // Bad <div> <span>Home</span> <span>About</span> </div> """ ### 4.2. ARIA Attributes **Do This:** * Use ARIA attributes to enhance the accessibility of custom components or when semantic HTML elements are not sufficient. * Ensure that ARIA attributes are used correctly and do not conflict with native HTML semantics. **Don't Do This:** * Use ARIA attributes unnecessarily or incorrectly. * Override native HTML semantics with ARIA attributes unless absolutely necessary. **Why:** ARIA attributes provide additional information to assistive technologies, allowing them to understand the role, state, and properties of components. **Example:** """tsx // Custom Button <button aria-label="Close dialog" onClick={onClose}> <span aria-hidden="true">×</span> {/* Times Symbol */} </button> """ ### 4.3. Keyboard Navigation **Do This:** * Ensure that all interactive elements are accessible via keyboard navigation. * Use "tabIndex" attribute to control the focus order of elements. * Provide visual focus indicators for keyboard users. **Don't Do This:** * Create components that can only be interacted with using a mouse. * Remove the default focus outline without providing an alternative visual indicator. **Why:** Keyboard navigation is essential for users who cannot use a mouse or other pointing device. **Example:** """tsx <button tabIndex={0} onClick={onClick}> Click me </button> """ ### 4.4. Color Contrast **Do This:** * Ensure sufficient color contrast between text and background colors. * Use a color contrast checker tool to verify that your color combinations meet accessibility standards (WCAG). **Don't Do This:** * Use color combinations that are difficult to read or cause eye strain. * Rely solely on color to convey important information. **Why:** Adequate color contrast is crucial for users with low vision or color blindness. ## 5. Testing ### 5.1. Unit Tests **Do This:** * Write unit tests for individual components to verify that they function as expected. * Use testing libraries like Jest and React Testing Library. * Aim for high test coverage to ensure that all critical code paths are tested. **Don't Do This:** * Skip unit tests for components with complex logic. * Write tests that are too tightly coupled to the implementation details of components. **Why:** Unit tests provide confidence that your components are working correctly and help prevent regressions when making changes. **Example:** """tsx // Button.test.tsx import { render, screen, fireEvent } 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(); }); test('calls onClick handler when clicked', () => { const onClick = jest.fn(); render(<Button onClick={onClick}>Click me</Button>); const buttonElement = screen.getByText('Click me'); fireEvent.click(buttonElement); expect(onClick).toHaveBeenCalledTimes(1); }); """ ### 5.2. Integration Tests **Do This:** * Write integration tests to verify that components work together correctly. * Simulate user interactions and verify that the application behaves as expected. **Don't Do This:** * Skip integration tests for complex components. * Rely solely on unit tests without verifying the integration of components. **Why:** Integration tests ensure that different parts of the application work together seamlessly. ### 5.3. End-to-End (E2E) Tests **Do This:** * Write E2E tests to verify the end-to-end functionality of the application. * Use tools like Cypress or Playwright to automate browser interactions. * Focus on testing critical user flows and scenarios. **Don't Do This:** * Skip E2E tests for important features. * Write E2E tests that are too brittle or flaky. **Why:** E2E tests provide confidence that the application is working correctly from the user's perspective. ## 6. Performance Optimization ### 6.1. Memoization **Do This:** * Use "React.memo" to prevent unnecessary re-renders of components. * Ensure that the props passed to memoized components are memoized as well. * Use "useMemo" and "useCallback" hooks to memoize computed values and callback functions. * If using Shadcn, many components are already "memoized" but it's important to understand in depth when creating your own. **Don't Do This:** * Overuse memoization, as it can introduce unnecessary complexity. * Memoize components that always receive new props. **Why:** Memoization can significantly improve performance by preventing unnecessary re-renders of components. **Example:** """tsx import React, { memo } from 'react'; interface MyComponentProps { data: any; onClick: () => void; } const MyComponent = ({ data, onClick }: MyComponentProps) => { console.log('MyComponent rendered'); return ( <button onClick={onClick}> {data.name} </button> ); }; export default memo(MyComponent); """ ### 6.2. Code Splitting **Do This:** * Use dynamic imports to split the application code into smaller chunks. * Load code chunks on demand to reduce the initial load time. * Consider using React.lazy and Suspense for code splitting. **Don't Do This:** * Load all the application code upfront. * Create too many small code chunks, as it can increase network overhead. **Why:** Code splitting reduces the initial load time and improves the perceived performance of the application. **Example:** """tsx import React, { lazy, Suspense } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> ); } """ ### 6.3. Image Optimization **Do This:** * Optimize images by compressing them and using appropriate file formats (e.g., WebP). * Use responsive images to serve different image sizes based on the user's device. * Lazy load images that are not immediately visible. **Don't Do This:** * Use large, unoptimized images. * Load all images upfront, even those that are not visible. **Why:** Image optimization reduces the page size and improves the loading speed. ## 7. Security ### 7.1. Input Validation **Do This:** * Validate all user inputs to prevent security vulnerabilities such as Cross-Site Scripting (XSS) and SQL Injection. * Use server-side validation in addition to client-side validation. * Sanitize user inputs to remove or encode potentially harmful characters. **Don't Do This:** * Trust user inputs without validation. * Rely solely on client-side validation for security. **Why:** Input validation prevents attackers from injecting malicious code or data into the application. ### 7.2. Dependency Management **Do This:** * Keep dependencies up to date to patch security vulnerabilities. * Use a dependency management tool (e.g., npm or yarn) to manage dependencies. * Regularly audit dependencies for known vulnerabilities. **Don't Do This:** * Use outdated dependencies with known security vulnerabilities. * Ignore security warnings from dependency management tools. **Why:** Dependency management ensures that the application is using secure versions of its dependencies. Shadcn releases updates frequently and it's important to follow. ### 7.3. Secure API Communication **Do This:** * Use HTTPS to encrypt communication between the client and the server. * Implement proper authentication and authorization mechanisms to protect API endpoints. * Use JSON Web Tokens (JWT) for authentication. **Don't Do This:** * Use HTTP for sensitive data transmission. * Expose API keys or secrets in client-side code. **Why:** Secure API communication protects sensitive data from being intercepted or tampered with. ### 7.4. Sanitize HTML **Do This:** * Sanitize HTML for any user inputted components. * Before rendering any user-supplied HTML content, sanitize it with a library like DOMPurify to prevent XSS * Use a strict Content Security Policy (CSP) to mitigate XSS attacks. - Set appropriate CSP headers on your server to control the sources from which the browser is allowed to load resources **Don't Do This:** * Render unsanitized user input directly to the DOM, which can execute malicious scripts * Allow inline scripts or styles in your CSP, as this is a common attack vector **Why:** Even the simple act of rendering a string that a user entered can lead to XSS attacks. It's important to take precautionary measures to ensure this doesn't occur. **Example:** """tsx import DOMPurify from 'dompurify'; function MyComponent({ userInput }: { userInput: string }) { const sanitizedHTML = DOMPurify.sanitize(userInput); return <div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />; } """ This document provides guidelines for writing maintainable, performant, and secure Shadcn components. Following these practices will improve the overall quality of your codebase and facilitate collaboration within your development team.
# 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.
# 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.
# 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 ( <ThemeContext.Provider value={{ state, dispatch }}> {children} </ThemeContext.Provider> ); }; 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 ( <div> <p>Dark Mode: {state.darkMode ? 'On' : 'Off'}</p> <Button onClick={() => dispatch({ type: 'TOGGLE_DARK_MODE' })}> Toggle Dark Mode </Button> </div> ); }; 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 ( <div> <p>Count: {count}</p> <Button onClick={increment}>Increment</Button> <Button onClick={decrement}>Decrement</Button> <Button onClick={reset}>Reset</Button> </div> ); }; 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 ( <div> <p>Count: {count}</p> <Button onClick={() => dispatch(increment())}>Increment</Button> <Button onClick={() => dispatch(decrement())}>Decrement</Button> <Button onClick={() => dispatch(incrementByAmount(5))}>Increment by 5</Button> </div> ); }; 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 ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }; 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 ( <div> <p>Parent Message: {message}</p> <ChildComponent message={message} onMessageChange={handleMessageChange} /> </div> ); }; 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 ( <div> <Input type="text" value={message} onChange={handleChange} /> </div> ); }; 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 ( <div> <Input type="text" value={message} onChange={handleChange} /> </div> ); }; 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 ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ); }; export default ItemList; """ """jsx // Incorrect const items = [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }, ]; const BadItemList = () => { return ( <ul> {items.map((item, index) => ( <li key={index}>{item.name}</li> ))} </ul> ); }; 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 ( <button onClick={onClick}> {data.name} </button> ); }; 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 ( <div> <p>Count: {count}</p> <MyComponent data={data} onClick={handleClick} /> <button onClick={() => setCount(count + 1)}>Increment Parent</button> </div> ); }; 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 ( <div> <Button onClick={() => setIsVisible(!isVisible)}> {isVisible ? 'Hide' : 'Show'} </Button> {isVisible && <p>This content is visible.</p>} </div> ); }; 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 ( <Button disabled={isPending} onClick={handleClick}> {isLiked ? 'Unlike' : 'Like'} {isPending ? 'Updating...' : ''} </Button> ); }; 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 ( <div> <p>Count: {count}</p> <Button onClick={increment}>Increment</Button> </div> ); }; 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 ( <div> <p>Count: {count}</p> <Button onClick={increment}>Increment</Button> <Button onClick={decrement}>Decrement</Button> </div> ); }; 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 ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }; 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 ( <div> <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> </div> ); }; 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 }) => ( <div style={style}>Row {index}</div> ); const VirtualizedList = () => { return ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> ); }; 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 ( <div> <Input type="text" placeholder="Search..." value={searchTerm} onChange={handleChange} /> </div> ); }; 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.
# Performance Optimization Standards for Shadcn This document outlines performance optimization standards for Shadcn projects. It provides guidelines, best practices, and code examples to help developers build fast, responsive, and resource-efficient applications using Shadcn. ## 1. Data Fetching and Caching Strategies Efficient data fetching and caching are critical for minimizing network requests and improving application load times. ### 1.1. Server-Side Rendering (SSR) or Static Site Generation (SSG) **Standard:** Use SSR or SSG whenever possible for content that doesn't require frequent updates or personalized data. This enables faster initial page loads as the HTML is pre-rendered on the server or during build time. **Why:** SSR and SSG reduce client-side JavaScript execution, leading to faster First Contentful Paint (FCP) and Largest Contentful Paint (LCP). This dramatically improves perceived performance, especially on low-powered devices or slow network connections. **Do This:** * Use Next.js with Shadcn to leverage SSR and SSG capabilities. * In "app/page.tsx", use "async" functions for data fetching in server components. **Don't Do This:** * Default to Client-Side Rendering (CSR) for content that can be pre-rendered. * Fetch data only on the client when initial page load performance is critical. **Example:** """tsx // app/page.tsx import { db } from "@/lib/db"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; async function getData() { // Simulate fetching posts from a database const posts = await db.post.findMany({ orderBy: { createdAt: 'desc' } }); return posts; } export default async function Home() { const posts = await getData(); return ( <div className="container mx-auto py-10"> <Card> <CardHeader> <CardTitle>Latest Posts</CardTitle> </CardHeader> <CardContent> {posts.map((post) => ( <div key={post.id} className="mb-4"> <h3>{post.title}</h3> <p>{post.content}</p> </div> ))} </CardContent> </Card> </div> ); } """ **Anti-pattern:** Fetching data on the client using "useEffect" for the initial page render when SSR or SSG is viable. ### 1.2. Caching Data **Standard:** Implement caching strategies both on the server and client to avoid redundant data fetching. **Why:** Caching reduces the load on backend servers and improves response times for frequently accessed data. Server-side caching also assists in preventing overages on database queries. **Do This:** * Utilize Next.js's built-in data caching mechanisms (e.g., "fetch" API with "revalidate" options). * Implement client-side caching using libraries like "swr" or "react-query" for frequently accessed data. * Use Incremental Static Regeneration (ISR) to periodically update static content without redeploying. **Don't Do This:** * Disable caching unnecessarily, leading to repeated data fetching. * Cache sensitive data without proper security measures. **Example (Next.js Server Component Caching):** """tsx // app/components/PostsList.tsx import { unstable_cache } from 'next/cache'; import { db } from "@/lib/db"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; const getPosts = unstable_cache( async () => { const posts = await db.post.findMany({ orderBy: { createdAt: 'desc' } }); return posts; }, ['posts'], //cache tag - clears cache when revalidated { revalidate: 60, // Revalidate the cache every 60 seconds } ); export async function PostsList() { const posts = await getPosts(); return ( <CardContent> {posts.map((post) => ( <div key={post.id} className="mb-4"> <h3>{post.title}</h3> <p>{post.content}</p> </div> ))} </CardContent> ); } """ **Example (Client-Side Caching with "swr"):** """tsx // app/components/UserProfile.tsx import useSWR from 'swr'; import { fetcher } from '@/lib/utils'; // Assume this is your fetcher function interface User { id: string; name: string; email: string; } function UserProfile({ userId }: { userId: string }) { const { data: user, error, isLoading } = useSWR<User>("/api/users/${userId}", fetcher); if (isLoading) return <div>Loading...</div>; if (error) return <div>Failed to load user</div>; return ( <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> ); } export default UserProfile; """ **Technology-Specific Detail:** Next.js 13+ offers built-in caching at the route segment level, so you can cache data fetches and even entire React Server Components. This is much easier to implement than custom caching solutions. ### 1.3. Pagination and Infinite Scrolling **Standard:** Implement pagination or infinite scrolling for large datasets to load data in manageable chunks. **Why:** Loading an entire dataset at once can lead to performance bottlenecks and a poor user experience. Pagination and infinite scrolling improve initial load times and reduce memory consumption. **Do This:** * Use server-side pagination to limit the number of items returned per request. * Implement infinite scrolling with libraries like "react-infinite-scroll-component" for a seamless user experience. * Provide loading indicators to inform users that more data is being fetched. **Don't Do This:** * Load the entire dataset at once, especially for large datasets. * Neglect to provide loading indicators, leading to a confusing user experience. **Example (Pagination):** """tsx // app/components/PostsList.tsx import { db } from "@/lib/db"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; interface Post { id: string; title: string; content: string; } async function getPaginatedPosts(page: number, pageSize: number): Promise<Post[]> { const skip = (page - 1) * pageSize; const posts = await db.post.findMany({ skip, take: pageSize, orderBy: { createdAt: 'desc' } }); return posts; // return data } interface Props { page: number; pageSize: number; } export async function PostsList({ page, pageSize }:Props) { const posts = await getPaginatedPosts(page, pageSize); return ( <CardContent> {posts.map((post) => ( <div key={post.id} className="mb-4"> <h3>{post.title}</h3> <p>{post.content}</p> </div> ))} </CardContent> ); } """ ### 1.4 Image Optimization **Standard:** Optimize images for the web to reduce file sizes and improve loading times. **Why:** Large, unoptimized images significantly impact page load times and bandwidth usage. Efficient image optimization is an essential aspect of performance management and a great use of caching as well. **Do This:** * Use optimized image formats like WebP or AVIF. * Implement responsive images with the "<Image>" component from "next/image" to serve different sizes based on the user's device. This component handles lazy loading automatically. * Compress images without sacrificing too much quality (use tools like ImageOptim or TinyPNG). * Use a Content Delivery Network (CDN) for image storage and delivery. **Don't Do This:** * Upload large, unoptimized images directly to your server. * Serve the same image size to all devices, wasting bandwidth on smaller screens. * Skip the "alt" attribute on images, which is crucial for accessibility and SEO. **Example:** """tsx // app/components/Hero.tsx import Image from 'next/image'; export default function Hero() { return ( <div className="relative h-96 w-full"> <Image src="/images/hero.jpg" // Local image, can also be a URL alt="A beautiful landscape" layout="fill" // Required attribute objectFit="cover" // Adjust how the image fills the space quality={75} //optimize using compression priority //preload important images /> <div className="absolute inset-0 flex items-center justify-center"> <h1 className="text-4xl font-bold text-white">Welcome to our Website</h1> </div> </div> ); } """ **Technology Specific Detail:** "next/image" handles much of the complexity of image optimization transparently. When using remote images, you MUST configure "next.config.js" to allow the image domains. Alternatively, use a service like Cloudinary or Imgix that handle this automatically. ## 2. Code Splitting and Lazy Loading Code splitting and lazy loading help reduce the initial JavaScript bundle size, improving application startup time. ### 2.1. Dynamic Imports **Standard:** Use dynamic imports ("import()") to load components or modules only when needed. **Why:** Dynamic imports split the code into smaller chunks loaded on demand, reducing the initial bundle size and improving time-to-interactive (TTI). **Do This:** * Use "React.lazy" and "Suspense" for lazy loading components. * Dynamically import large libraries or modules that are not immediately required. * Use "next/dynamic" for lazy loading components from the "pages" directory (if using the older "pages" router). * In the app directory, you can use "React.lazy" in client components. **Don't Do This:** * Import all modules upfront, increasing the initial bundle size. * Overuse lazy loading, as it can introduce network overhead and negatively impact user experience if done excessively. **Example:** """tsx // app/components/MyComponent.tsx (client component) 'use client'; import React, { Suspense, lazy } from 'react'; import { Skeleton } from "@/components/ui/skeleton" const LazyComponent = lazy(() => import('./HeavyComponent')); function MyComponent() { return ( <div> <h1>My Component</h1> <Suspense fallback={<Skeleton className="w-[200px] h-[20px]" />}> <LazyComponent /> </Suspense> </div> ); } export default MyComponent; """ """tsx // app/components/HeavyComponent.tsx export default function HeavyComponent() { // This component contains complex logic or a large library import return ( <div> <h2>This is a Heavy Component</h2> <p>Loaded dynamically!</p> </div> ); } """ **Anti-pattern:** Loading all components eagerly in the main application bundle. ### 2.2. Route-Based Code Splitting **Standard:** Leverage Next.js's automatic route-based code splitting. **Why:** Route-based code splitting ensures that only the code required for the current route is loaded, reducing initial load times. This is done automatically in the app directory. **Do This:** * Organize your application into separate routes or pages. * Ensure that you code is properly bundled for each unique URL path. **Don't Do This:** * Create a single, monolithic application with all code loaded upfront. **Example:** (This is more of an organizational principle than a code example, but crucial for performance.) * "/app/page.tsx" - Home page * "/app/products/page.tsx" - Products page * "/app/blog/page.tsx" - Blog page Each of these routes (pages) will be in its own code split automatically. ## 3. Optimizing React Components Optimizing React components can significantly improve rendering performance and responsiveness. ### 3.1. Memoization **Standard:** Use "React.memo" to prevent unnecessary re-renders of pure components. **Why:** "React.memo" memoizes the rendered output of a component and only re-renders it if the props have changed. This avoids wasteful rendering cycles, especially for components that receive props from parent components that re-render frequently. **Do This:** * Wrap pure components with "React.memo". * Use "useMemo" and "useCallback" hooks to memoize calculated values and event handlers, respectively. **Don't Do This:** * Memoize components unnecessarily, as the memoization process itself has a cost. * Forget to update the memoization key when the component's dependencies change. **Example:** """tsx // app/components/MyComponent.tsx import React, { useState, useCallback } from 'react'; import { Button } from "@/components/ui/button" interface Props { name: string; onClick: () => void; } const MyComponent = React.memo(function MyComponent({ name, onClick }: Props) { console.log("Rendering MyComponent with name: ${name}"); return ( <Button onClick={onClick}> Hello, {name}! </Button> ); }); function ParentComponent() { const [count, setCount] = useState(0); //useCallback to prevent function from being recreated on every render const handleClick = useCallback(() => { setCount((prevCount) => prevCount + 1); }, []); return ( <div> <p>Count: {count}</p> {/* my component is memoized so only renders when name prop changes */} <MyComponent name="World" onClick={handleClick} /> <Button onClick={() => setCount((prevCount) => prevCount + 1)}>Increment</Button> </div> ); } export default ParentComponent; """ **Anti-pattern:** Omitting "useCallback" for event handlers passed as props to memoized components. ### 3.2. Virtualization **Standard:** Use virtualization libraries like "react-window" or "react-virtualized" for rendering large lists of data. **Why:** Virtualization renders only the visible portion of a list, improving performance for lists with thousands of items. This prevents the browser from being overloaded by rendering elements that are not currently on screen. **Do This:** * Use virtualization for long lists where performance is critical. * Adjust the virtualization settings (e.g., row height, overscan count) to optimize performance and user experience. **Don't Do This:** * Render all items in a long list without virtualization. * Use virtualization for short lists, as the overhead may outweigh the benefits. **Example:** """tsx // app/components/MyList.tsx import React from 'react'; import { FixedSizeList as List } from 'react-window'; interface Props { items: string[]; } function Row({ index, style, data }: { index: number; style: React.CSSProperties; data: Props["items"] }) { const item = data[index]; return ( <div style={style} className="p-2 border-b"> {"Row ${index}: ${item}"} </div> ); } function MyList({ items }: Props) { return ( <List height={400} itemCount={items.length} itemSize={35} width={300} data={items} > {Row} </List> ); } export default MyList; """ ### 3.3. Avoiding Unnecessary Re-renders **Standard:** Prevent unnecessary re-renders by carefully managing component state and props. **Why:** Frequent and unnecessary re-renders can cause performance issues, especially for complex components or when rendering large lists. **Do This:** * Use immutable data structures to easily detect changes in state and props. * Avoid mutating state directly; use the "setState" function or the spread operator. * Optimize context providers to prevent unnecessary updates to context consumers. * Use tools such as the React Profiler to measure component render times and identify bottlenecks. **Don't Do This:** * Mutate state directly, leading to unexpected re-renders. * Update context values unnecessarily, causing all consumers to re-render. **Example(Avoiding Mutation):** """tsx // Avoid direct mutation for a better component update lifecycle import React, {useState} from 'react'; function MyComponent() { const [items, setItems] = useState([{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }]); // ❌ Don't do this: mutating the array directly const addItemBad = () => { items.push({ id: items.length + 1, name: "Item ${items.length + 1}" }); setItems(items); // Doesn't trigger a re-render because the reference is the same }; // ✅ Do this: create a new array with the new item const addItemGood = () => { setItems([...items, { id: items.length + 1, name: "Item ${items.length + 1}" }]); // Creates a new array }; return ( <> <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> <button onClick={addItemGood}>Add Item (Correct)</button> </> ); } export default MyComponent; """ ### 3.4. Using Keys Effectively **Standard:** Provide unique and stable keys for list items. **Why:** Keys help React identify which items have changed, been added, or been removed in a list. Using the wrong keys can lead to unnecessary re-renders or incorrect component behavior. **Do This:** * Use unique and stable identifiers as keys (e.g., IDs from a database). * Avoid using array indexes as keys when the list is dynamic. **Don't Do This:** * Use random values as keys. * Use array indexes as keys when the list items can be reordered or filtered. **Example:** """tsx // app/components/MyList.tsx import React from 'react'; interface Item { id: string; name: string; } interface Props { items: Item[]; } function MyList({ items }: Props) { return ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> // ✅ Use unique and stable IDs as keys ))} </ul> ); } export default MyList; """ ## 4. Shadcn Component Considerations While Shadcn provides pre-built, accessible components styled with Tailwind CSS, some aspects require attention for optimal performance. ### 4.1 Tailwind CSS Optimization **Standard:** Purge unused Tailwind CSS classes **Why:** Tailwind CSS generates a large CSS file. Purging ensures only the CSS classes used in your project are included in the production build, reducing the file size. Shadcn components use tailwind so this is very important **Do This:** * Configure "purge" in your "tailwind.config.js" or "tailwind.config.ts" file to specify which files to scan for CSS classes. **Don't Do This:** * Skip purging CSS classes, leading to a large and inefficient CSS file. * Purge too aggressively, removing CSS classes that are actually used. **Example tailwind.config.js:** """javascript /** @type {import('tailwindcss').Config} */ module.exports = { purge: [ './app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', './src/**/*.{js,ts,jsx,tsx}', //for example ], darkMode: 'media', // or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [], } """ ### 4.2. Component Composition for Reusability **Standard:** Favor composition over deep nesting within Shadcn components. **Why:** Excessive nesting of components can increase the rendering cost and make it harder to optimize re-renders. Composition promotes modularity and makes it easier to isolate and optimize individual components. **Do This:** * Break down complex Shadcn components into smaller, reusable components. * Use composition (passing children as props) to create flexible and composable interfaces. * Aim for a shallow component tree to minimize rendering overhead. **Don't Do This:** * Create deeply nested component structures. * Pass large amounts of data as props to deeply nested components. **Example:** Instead of: """tsx // app/components/ComplexForm.tsx import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; function ComplexForm() { return ( <form className="space-y-4"> <div> <Label htmlFor="name">Name:</Label> <Input type="text" id="name" /> </div> <div> <Label htmlFor="email">Email:</Label> <Input type="email" id="email" /> </div> <div> <Label htmlFor="password">Password:</Label> <Input type="password" id="password" /> </div> <Button>Submit</Button> </form> ); } export default ComplexForm; """ Do This: """tsx // app/components/FormInput.tsx import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; interface Props { label: string; id: string; type: string; } function FormInput({ label, id, type }: Props) { return ( <div> <Label htmlFor={id}>{label}:</Label> <Input type={type} id={id} /> </div> ); } export default FormInput; """ """tsx // app/components/ComplexForm.tsx import { Button } from "@/components/ui/button"; import FormInput from "./FormInput"; function ComplexForm() { return ( <form className="space-y-4"> <FormInput label="Name" id="name" type="text" /> <FormInput label="Email" id="email" type="email" /> <FormInput label="Password" id="password" type="password" /> <Button>Submit</Button> </form> ); } export default ComplexForm; """ The refactored code keeps components smaller and focused on their core functionality which helps with performance. ### 4.3 Conditional Rendering **Standard:** Use conditional rendering judiciously. **Why:** While conditional rendering is essential, excessive or deeply nested conditional logic can hinder performance. It increases the complexity of rendering and can make it more difficult to optimize. **Do this:** * Use short-circuit evaluation ("&&", "||") for simple conditional rendering scenarios. * Extract complex conditional logic into separate components. * Use the "React.Fragment" ("<> </>") or "React.createElement" when rendering null or multiple elements conditionally. * Use "useMemo" to memoize parts of the template that should not be re rendered. **Don't Do this:** * Create deeply nested conditional structures that make it difficult to follow the rendering flow and can degrade performance. **Example:** """tsx // app/components/ConditionalComponent.tsx import React, {useState} from 'react'; import { Button } from "@/components/ui/button" function ConditionalComponent() { const [isLoading, setIsLoading] = useState(false); // ✅ Short-circuit evaluation for simple rendering return ( <> {isLoading ? ( // Use the ternary operator for explicit conditions <p>Loading...</p> ) : ( <Button onClick={() => setIsLoading(true)}>Load Data</Button> )} </> ); } export default ConditionalComponent; """ ## 5. Monitoring and Tooling **Standard:** Regularly monitor application performance and use profiling tools to identify and address bottlenecks. **Why:** Monitoring and profiling provide valuable insights into application behavior and help you identify areas for optimization. **Do This:** * Use the React Profiler to measure component render times and identify performance bottlenecks. * Monitor key performance metrics like FCP, LCP, and TTI using tools like Google PageSpeed Insights or WebPageTest. * Use browser developer tools to analyze network requests, JavaScript execution, and memory usage. * Implement logging and error tracking to identify and resolve performance-related issues in production. **Don't Do This:** * Ignore performance issues until they become critical problems. * Rely solely on manual testing to identify performance bottlenecks. * Fail to monitor application performance in production. **Example:** 1. **Using React Profiler:** In development mode, use the React Profiler in the React DevTools to identify slow-rendering components. 2. **Performance Metrics:** Use PageSpeed Insights to get a high-level overview of your application's performance and suggestions for improvement. 3. **Browser DevTools:** Use the Network tab to analyze network requests and identify large files or slow API calls. Use the Performance tab to profile JavaScript execution and identify bottlenecks. By adhering to these performance optimization standards, Shadcn developers can build fast, responsive, and efficient applications that provide a great user experience. Regularly review and update these standards to stay current with the latest best practices and technologies.