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