# API Integration Standards for Vercel
This document outlines the coding standards for API integrations within Vercel projects. Following these guidelines will ensure maintainable, performant, and secure applications deployed on Vercel. These standards are tailored for modern Vercel development, utilizing its serverless functions, edge middleware, and platform features effectively.
## 1. Architectural Patterns for API Integration
Choosing the right architectural pattern is crucial for API integration within Vercel. These patterns must address scalability, security, and maintainability within the serverless environment.
### 1.1 Backend for Frontend (BFF) Pattern
**Description:** The BFF pattern involves creating an intermediary API layer tailored to specific client needs. This avoids exposing the full complexity of your backend directly to the client and allows for client-specific optimizations.
**Why:** Decouples client applications from backend services, improving client application performance, security and developer experience. Centralizes data aggregation and transformation, reducing complexity in client code.
**Do This:**
* Create BFFs as Vercel serverless functions or Edge Functions.
* Tailor data fetching and transformation within the BFF to the specific needs of the client application.
* Use environment variables to configure backend service endpoints.
* Implement caching strategies within the BFF to reduce latency and backend load (Vercel's Data Cache is ideal).
**Don't Do This:**
* Expose backend services directly to the client.
* Create overly generic BFFs that don't cater to specific client needs.
* Hardcode backend service endpoints in the client application.
**Example:**
"""typescript
// pages/api/bff/products.ts (Next.js API Route as BFF on Vercel)
import { NextApiRequest, NextApiResponse } from 'next';
const API_ENDPOINT = process.env.PRODUCT_API_URL;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const response = await fetch("${API_ENDPOINT}/products", {
headers: {
"Authorization": "Bearer ${process.env.PRODUCT_API_KEY}",
"Content-Type": "application/json"
}
});
if (!response.ok) {
throw new Error("HTTP error! Status: ${response.status}");
}
const data = await response.json();
// Transform the data specifically for the client application
const transformedData = data.map((product: any) => ({
id: product.id,
name: product.name,
price: "$${product.price.toFixed(2)}",
imageUrl: product.imageUrl,
}));
res.status(200).json(transformedData);
} catch (error: any) {
console.error("Error fetching products:", error);
res.status(500).json({ error: 'Failed to fetch products' });
}
}
"""
### 1.2 API Gateway Pattern
**Description:** Use an API Gateway (such as Vercel's own middlware or a third-party service) to route incoming requests, handle authentication, rate limiting, and other cross-cutting concerns before forwarding them to backend services within Vercel's infrastructure.
**Why:** Provides a centralized point of control for managing API access, improving security and observability. Enforces consistent policies across all APIs. Simplifies backend services by offloading cross-cutting concerns.
**Do This:**
* Implement authentication, authorization, and rate limiting at the API Gateway level using Vercel Middleware, or a dedicated gateway service (e.g., Kong, Tyk).
* Use the API Gateway to transform requests and responses, if necessary, for compatibility between client applications and backend services.
* Configure the API Gateway to route requests based on URL paths and HTTP methods.
* Log all API requests and responses at the API Gateway for auditing and monitoring purposes.
**Don't Do This:**
* Implement authentication and authorization logic in each individual backend service.
* Expose backend services directly to the public internet.
* Overload the API Gateway with complex business logic.
**Example:**
"""typescript
// middleware.ts (Vercel Middleware serving as an API Gateway)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const AUTH_TOKEN = process.env.AUTH_TOKEN;
export async function middleware(req: NextRequest) {
const authHeader = req.headers.get('authorization');
if (!authHeader || authHeader !== "Bearer ${AUTH_TOKEN}") {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}
return NextResponse.next();
}
export const config = {
matcher: '/api/:path*', // Apply this middleware to all API routes
}
"""
## 2. Secure API Integrations
Security must be paramount when integrating with APIs.
### 2.1 Authentication and Authorization
**Description:** Verify the identity of API clients and ensure they have the necessary permissions to access resources using robust authentication and authorization mechanisms.
**Why:** Prevents unauthorized access to sensitive data and system resources. Maintains data integrity and confidentiality.
**Do This:**
* Use API keys, JWT (JSON Web Tokens), or OAuth 2.0 for authentication.
* Store sensitive credentials (API keys, secrets) as environment variables within Vercel and grant access to specific serverless functions as needed.
* Implement role-based access control (RBAC) to restrict access to resources based on user roles and permissions.
* Validate and sanitize all API inputs to prevent injection attacks.
**Don't Do This:**
* Hardcode credentials in the codebase.
* Store credentials in client-side code.
* Grant excessive permissions to API clients.
* Trust user input without validation.
**Example:**
"""typescript
// pages/api/secured-resource.ts
import { NextApiRequest, NextApiResponse } from 'next';
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET!; // Ensure JWT_SECRET is set in Vercel
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Unauthorized: No token provided' });
}
try {
const decoded = jwt.verify(token, JWT_SECRET) as { userId: string, role: string }; // TypeScript assertion for decoded JWT
//Perform role-based access control
if (decoded.role !== 'admin') {
return res.status(403).json({ message: 'Forbidden: Insufficient privileges' });
}
//Access allowed, proceed with the resource request
res.status(200).json({ message: 'Access granted' });
} catch (error) {
return res.status(401).json({ message: 'Unauthorized: Invalid token' });
}
}
"""
### 2.2 Handling Secrets
**Description:** Store and manage API keys, tokens, and other sensitive information securely using Vercel's environment variables and secrets management features.
**Why:** Protects sensitive information from exposure in the codebase or configuration files. Prevents unauthorized access to backend services and APIs.
**Do This:**
* Store all API keys and secrets as environment variables within the Vercel project settings.
* Use Vercel's Secrets feature or similar providers for more advanced secret rotation and management.
* Avoid committing secrets to source control.
* Use separate environment variables for different environments (development, staging, production).
**Don't Do This:**
* Hardcode secrets in the codebase.
* Store secrets in configuration files that are committed to source control.
* Expose secrets in client-side code or API responses.
**Example:**
1. **Set environment variable in Vercel Project Settings:** "MY_API_KEY = "your_api_key""
2. **Access in Code:**
"""typescript
// pages/api/data.ts
import { NextApiRequest, NextApiResponse } from 'next';
const apiKey = process.env.MY_API_KEY;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!apiKey) {
console.error("API Key not found in environment variables");
return res.status(500).json({ error: "API Key not configured" });
}
// ... Use the apiKey to authenticate with the external API ...
}
"""
### 2.3 Input Validation
**Description:** Thoroughly validate and sanitize all data received from API requests to prevent injection attacks, cross-site scripting (XSS), and other security vulnerabilities.
**Why:** Prevents malicious users from injecting malicious code or manipulating data. Ensures data integrity and application stability.
**Do This:**
* Use validation libraries like Zod, Yup, or Joi to define schemas for API request bodies and query parameters.
* Sanitize all user-provided data to remove or escape potentially harmful characters.
* Implement rate limiting to prevent denial-of-service (DoS) attacks.
* Log and monitor all API input validation failures.
**Don't Do This:**
* Trust user input without validation.
* Allow users to submit arbitrary code or commands via API requests.
* Expose sensitive information in error messages.
"""typescript
// Example: Validating the API Request Body using Zod
import { NextApiRequest, NextApiResponse } from 'next';
import { z } from 'zod';
// Define a schema for the request body
const productSchema = z.object({
name: z.string().min(3).max(255),
description: z.string().optional(),
price: z.number().positive(),
imageUrl: z.string().url().optional(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method Not Allowed' });
}
try {
// Parse and validate the request body against the schema
const productData = productSchema.parse(req.body);
// Process the validated data
// ... Save the product to the database ...
res.status(201).json({ message: 'Product created successfully', data: productData });
} catch (error: any) {
console.error("Validation error:", error); // More careful error logging
if (error instanceof z.ZodError) {
return res.status(400).json({ message: 'Validation error', errors: error.errors });
}
res.status(400).json({ message: 'Invalid request body', error: error.message });
}
}
"""
## 3. Performance Optimization
Optimizing API integrations for performance is crucial within Vercel's serverless environment.
### 3.1 Caching Strategies
**Description:** Implement caching mechanisms at various levels (client-side, CDN, server-side) to reduce latency and improve API response times for frequently accessed data.
**Why:** Minimizes redundant API calls and reduces load on backend services. Improves user experience by providing faster response times. Reduces costs by decreasing serverless function invocations. Vercel integrates particularly well with its own cache layers.
**Do This:**
* Use Vercel's Edge Cache for static assets and API responses that can be cached globally. Set appropriate "cache-control" headers.
* Utilize Vercel's Data Cache for frequently accessed data that requires more granular control over cache invalidation (e.g., product details, user profiles).
* Implement client-side caching using browser APIs (e.g., "localStorage", "sessionStorage", "Cache API") for data that is specific to the user.
* Use a distributed cache (e.g., Redis, Memcached) for server-side caching of data that is shared across multiple serverless function instances.
**Don't Do This:**
* Cache sensitive data without proper encryption and access control.
* Set overly long cache expiration times, which can lead to stale data being served to users.
* Invalidate caches too aggressively, which can negate the benefits of caching.
**Example:**
"""typescript
// pages/api/cached-data.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { unstable_cache } from 'next/cache';
const fetchData = async (key: string) => {
console.log("Fetching data from external API...");
const response = await fetch("https://external-api.com/data?key=${key}");
if (!response.ok) {
throw new Error("HTTP error! status: ${response.status}");
}
return response.json();
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const key = req.query.key as string; // Ensure key is a string
if (!key) {
return res.status(400).json({ error: "Missing key parameter" });
}
const cachedData = await unstable_cache(
async () => {
return fetchData(key);
},
["api-data-${key}"], // Cache key
{
revalidate: 60, // Revalidate cache every 60 seconds
tags: ["data-${key}"], // Tag for selective cache invalidation
}
)();
res.status(200).json(cachedData);
}
"""
### 3.2 Connection Pooling and Keep-Alive
**Description:** Optimize database and API connections by reusing existing connections instead of establishing new ones for each request, especially when connecting to backend databases or external APIs from Vercel serverless functions.
**Why:** Reduces the overhead of establishing new connections, which can be significant in a serverless environment. Improves API response times and reduces resource consumption.
**Do This:**
* Use connection pooling libraries or database drivers that support connection pooling.
* Configure HTTP clients to use keep-alive connections to reuse TCP connections for multiple requests. Libraries like "node-fetch" generally handle this automatically.
* Avoid closing connections explicitly after each request, unless necessary.
* Monitor connection pool usage and adjust pool size as needed.
**Don't Do This:**
* Create a new database connection for each API request.
* Close connections explicitly after each request.
* Use excessively large connection pools, which can consume too much memory.
* Fail to handle connection errors gracefully.
**Example:**
"""typescript
// Example with Prisma ORM and connection pooling
import { PrismaClient } from '@prisma/client';
let prisma: PrismaClient;
// Ensure only one PrismaClient instance is created during hot reloads
if (process.env.NODE_ENV === 'production') {
prisma = new PrismaClient();
} else {
if (!(global as any).prisma) {
(global as any).prisma = new PrismaClient();
}
prisma = (global as any).prisma;
}
export const db = prisma;
// Now you can use 'db' to interact with your database in API routes
"""
### 3.3 Minimize Payload Sizes
**Description:** Reduce the size of API request and response payloads to minimize network transfer times and improve API performance.
**Why:** Reduces the amount of data that needs to be transferred over the network, resulting in faster API response times. Decreases bandwidth consumption and reduces costs.
**Do This:**
* Compress API responses using gzip or Brotli compression.
* Minimize the amount of data included in API responses by only including the necessary fields. Use techniques like GraphQL or field selection in REST APIs.
* Optimize images and other binary data before including them in API responses.
* Use efficient data serialization formats (e.g., JSON, MessagePack).
**Don't Do This:**
* Include unnecessary data in API responses.
* Use inefficient data serialization formats (e.g., XML).
* Transmit uncompressed data over the network.
**Example**
"""typescript
//Example GZIP compression for API responses
import { createGzip } from 'zlib';
import { pipeline } from 'stream';
import { promisify } from 'util';
import { NextApiRequest, NextApiResponse } from 'next';
const pipe = promisify(pipeline);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const data = { message: 'Hello, world!', timestamp: Date.now() };
const jsonData = JSON.stringify(data);
// Set appropriate headers for content encoding and type
res.setHeader('Content-Encoding', 'gzip');
res.setHeader('Content-Type', 'application/json');
const gzip = createGzip();
const dataStream = Readable.from([jsonData]);
await pipe(dataStream, gzip, res); // Pipe the data through gzip and send the compressed response
} catch (error) {
console.error('Error compressing response:', error);
res.status(500).json({ error: 'Failed to compress response' });
}
}
"""
## 4. Error Handling and Logging
Robust error handling and logging are essential for debugging and monitoring API integrations.
### 4.1 Centralized Logging
**Description:** Implement centralized logging to collect and analyze logs from all API components in a single location using tools like Vercel's logging integration, Sentry, or Datadog.
**Why:** Provides a comprehensive view of API behavior and helps identify and diagnose issues quickly. Enables proactive monitoring and alerting.
**Do This:**
* Use a structured logging format (e.g., JSON) to make logs easier to parse and analyze.
* Include relevant context in log messages, such as request IDs, user IDs, and timestamps.
* Log errors, warnings, and informational messages at appropriate levels.
* Use correlation IDs to track requests across multiple services.
**Don't Do This:**
* Log sensitive information, such as passwords or API keys.
* Write logs to local files, which can be difficult to access and analyze in a serverless environment.
* Ignore errors or warnings.
**Example:**
"""typescript
// pages/api/example.ts
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// Simulate an error
throw new Error('This is a test error');
} catch (error: any) {
console.error('API Error:', error); // Logs to Vercel's logging system
// Or, using a third-party service like Sentry:
// Sentry.captureException(error);
res.status(500).json({ error: 'An error occurred' });
}
}
"""
### 4.2 Graceful Error Handling
**Description:** Implement graceful error handling to prevent application crashes and provide informative error messages to clients.
**Why:** Improves user experience and prevents data loss. Makes it easier to debug and resolve issues.
**Do This:**
* Use try-catch blocks to catch exceptions and handle them gracefully.
* Provide informative error messages to clients, including error codes and descriptions.
* Log errors with sufficient detail for debugging purposes.
* Implement fallback mechanisms to handle unexpected errors.
**Don't Do This:**
* Expose sensitive information in error messages.
* Ignore errors or allow them to crash the application.
* Propagate raw exceptions to clients.
**Example:**
"""typescript
// pages/api/data-fetching.ts
import { NextApiRequest, NextApiResponse } from 'next';
async function fetchDataFromAPI(url: string) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP error! status: ${response.status}");
}
return await response.json();
} catch (error) {
console.error("Error fetching data:", error);
throw error; // Re-throw the error to be handled by the main handler function
}
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const data = await fetchDataFromAPI('https://api.example.com/data');
res.status(200).json(data);
} catch (error: any) {
console.error("API Route Error:", error.message);
res.status(500).json({ error: 'Failed to fetch data from API' });
}
}
"""
## 5. Testing and Monitoring
Thorough testing and monitoring are crucial for ensuring the reliability and performance of API integrations on Vercel.
### 5.1 Unit and Integration Testing
**Description:** Write unit tests to verify the functionality of individual components and integration tests to ensure that different components work together correctly.
**Why:** Helps catch bugs early in the development process. Ensures that code changes do not introduce regressions. Improves code quality and maintainability.
**Do This:**
* Write unit tests for all critical code paths.
* Use a testing framework like Jest or Mocha.
* Mock external dependencies to isolate components during testing.
* Run tests automatically as part of the CI/CD pipeline.
**Don't Do This:**
* Skip writing tests.
* Write tests that are too complex or brittle.
* Rely solely on manual testing.
### 5.2 Monitoring and Alerting
**Description:** Implement monitoring and alerting to track the performance and availability of APIs and receive notifications when issues occur.
**Why:** Enables proactive detection and resolution of issues. Helps maintain service level agreements (SLAs). Provides insights into API usage and performance.
**Do This:**
* Monitor key metrics, such as response time, error rate, and request volume using Vercel Analytics, or a third-party monitoring solution like Datadog or New Relic.
* Set up alerts to notify you when metrics exceed predefined thresholds.
* Use health checks to verify the availability of APIs.
* Monitor logs for errors and warnings.
**Don't Do This:**
* Ignore monitoring data.
* Set up alerts that are too sensitive or too noisy.
* Fail to respond to alerts in a timely manner.
Following these API integration standards for Vercel will help you build robust, scalable, and secure applications that take full advantage of the Vercel platform. This document serves as a comprehensive guide for developers and can be used as context for AI coding assistants to ensure consistent best practices throughout the development lifecycle.
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'
# Security Best Practices Standards for Vercel This document outlines security best practices for developing and deploying applications on the Vercel platform. Adhering to these standards will reduce your application's risk profile, leading to more reliable and secure deployments. ## 1. Input Validation & Sanitization ### 1.1 Standard * **Do This:** Always validate and sanitize user inputs, regardless of origin (e.g., forms, query parameters, cookies, API calls, environment variables). Implement validation on both the client (for immediate feedback) and the server (for ultimate security). * **Don't Do This:** Trust user input implicitly. Never directly use unsanitized user input in database queries, shell commands, or rendered HTML. ### 1.2 Why * **Security:** Prevents various injection attacks (SQL injection, XSS, command injection) by ensuring data conforms to expected formats and removing potentially malicious content. It also helps to prevent Denial of Service (DoS) attacks. * **Maintainability:** Makes code more robust and predictable, as it handles unexpected input gracefully. * **Performance:** Rejecting invalid input early reduces unnecessary processing and resource consumption. ### 1.3 Code Examples #### 1.3.1 Server-Side Input Validation (Next.js API Route) """typescript // pages/api/submit-form.ts import { NextApiRequest, NextApiResponse } from 'next'; import validator from 'validator'; // Using a popular validator library export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).end(); // Method Not Allowed } const { name, email, message } = req.body; // Input validation if (!name || !validator.isLength(name, { min: 2, max: 100 })) { return res.status(400).json({ error: 'Name must be between 2 and 100 characters.' }); } if (!email || !validator.isEmail(email)) { return res.status(400).json({ error: 'Invalid email address.' }); } if (!message || !validator.isLength(message, { min: 10, max: 500 })) { return res.status(400).json({ error: 'Message must be between 10 and 500 characters.' }); } // Sanitize the input const sanitizedName = validator.escape(name); const sanitizedEmail = validator.normalizeEmail(email); const sanitizedMessage = validator.escape(message); // Process the sanitized data (e.g., store in database, send email) try { //Simulate database interaction to handle the submission // normally you would interact with your chosen DB console.log({ name: sanitizedName, email: sanitizedEmail, message: sanitizedMessage }) res.status(200).json({ success: true }); } catch (error) { console.error('Error processing form submission:', error); res.status(500).json({ error: 'Internal server error' }); } } """ * **Explanation:** * The code uses the "validator" library for robust and standardized validation. Install it with "npm install validator". * It checks data presence and length constraints. Specifically it checks that the "name" is between 2 and 100 characters, email is a valid email, and message is between 10 and 500 characters. * Uses "validator.escape()" to prevent XSS attacks by escaping HTML entities. * Uses "validator.normalizeEmail()" to standardize email addresses. * Error handling is included to gracefully handle unexpected errors. #### 1.3.2 Client-Side Input Validation (React Component) """jsx // components/ContactForm.tsx import React, { useState } from 'react'; const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const ContactForm = () => { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [message, setMessage] = useState(''); const [errors, setErrors] = useState({name: '', email: '', message: ''}); const validateForm = () => { let isValid = true; let newErrors = {name: '', email: '', message: ''}; if(name.length < 2 || name.length > 100){ newErrors.name = 'Name must be between 2 and 100 characters.'; isValid = false; } if(!EMAIL_REGEX.test(email)){ newErrors.email = 'Invalid email address.'; isValid = false; } if(message.length < 10 || message.length > 500){ newErrors.message = 'Message must be between 10 and 500 characters.'; isValid = false; } setErrors(newErrors); return isValid; } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { return; } try { const response = await fetch('/api/submit-form', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, email, message }), }); if (response.ok) { alert('Form submitted successfully!'); } else { alert('Form submission failed.'); } } catch (error) { console.error('Error submitting form:', error); alert('An error occurred while submitting the form.'); } }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="name">Name:</label> <input type="text" id="name" value={name} onChange={(e) => setName(e.target.value)} /> {errors.name && <p className="error">{errors.name}</p>} </div> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} /> {errors.email && <p className="error">{errors.email}</p>} </div> <div> <label htmlFor="message">Message:</label> <textarea id="message" value={message} onChange={(e) => setMessage(e.target.value)} /> {errors.message && <p className="error">{errors.message}</p>} </div> <button type="submit">Submit</button> </form> ); }; export default ContactForm; """ * **Explanation:** * This client-side validation provides immediate feedback to the user. * Built in regex variable called "EMAIL_REGEX" used to check structure of email. * The "validateForm" function checks for empty fields and invalid email formats. * Displays error messages to the user if validation fails. * Important: Client-side validation is *not* a substitute for server-side validation. ### 1.4 Anti-Patterns * **Blindly trusting environment variables:** While they are generally considered safe, validate environment variables received from external sources or user-provided configurations. Malicious actors could set unexpected environment variables to exploit vulnerabilities. * **Relying solely on client-side validation:** Always perform server-side validation, as client-side validation can be bypassed. * **Using insecure functions:** Avoid functions that are known to be vulnerable to injection attacks (e.g., "eval()" in JavaScript, "shell_exec()" in PHP) ## 2. Authentication and Authorization ### 2.1 Standard * **Do This:** Implement robust authentication and authorization mechanisms to protect sensitive resources. Use industry-standard protocols like OAuth 2.0 or OpenID Connect for authentication. Implement role-based access control (RBAC) to authorize users based on their roles and permissions. * **Don't Do This:** Store passwords in plain text. Implement your own custom authentication system without proper security expertise. Grant excessive privileges to users. ### 2.2 Why * **Security:** Protects sensitive data and functionality from unauthorized access. * **Maintainability:** Standardized authentication and authorization systems are easier to manage and audit. * **Compliance:** Many regulations require strong authentication and authorization controls. ### 2.3 Code Examples #### 2.3.1 Authentication with NextAuth.js (Recommended) """typescript // pages/api/auth/[...nextauth].ts import NextAuth from "next-auth" import GithubProvider from "next-auth/providers/github" import { PrismaAdapter } from "@next-auth/prisma-adapter" import { PrismaClient } from "@prisma/client" const prisma = new PrismaClient() export const authOptions = { adapter: PrismaAdapter(prisma), providers: [ GithubProvider({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, }), ], session: { strategy: "jwt" }, jwt: { secret: process.env.JWT_SECRET }, callbacks: { async session({ session, token, user }) { return { ...session, user: { ...session.user, id: user.id } } }, }, secret: process.env.NEXTAUTH_SECRET, } export default NextAuth(authOptions) """ * **Explanation:** * This example uses NextAuth.js, a popular library for adding authentication to Next.js applications. * It uses the GitHub provider for social login. You can configure other providers as well (Google, Facebook, etc.). * It uses "PrismaAdapter" from "@next-auth/prisma-adapter" and "@prisma/client" to persist user data in a database so you will need to install these dependencies. "npm install next-auth @next-auth/prisma-adapter @prisma/client" * The code above includes a "jwt" section to add JWT secret. * The "callbacks" section extends the session object with the user ID. This is critical for authorization logic. * Protect API routes with "getSession" from "next-auth/react". #### 2.3.2 Protecting API Routes with "getSession" """typescript // pages/api/protected.ts import { NextApiRequest, NextApiResponse } from 'next'; import { getSession } from 'next-auth/react'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({ req }); if (!session) { return res.status(401).json({ error: 'Unauthorized' }); } // Access the user id from the session const userId = session.user.id; // Your protected logic here using the user Id extracted from the session. res.status(200).json({ message: "Hello ${session.user.name}, your user id is ${userId}" }); } """ * The session contains user information if properly authenticated. #### 2.3.3 Role-Based Access Control (RBAC) """typescript // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' // Role list const roles = { admin: ['/admin'], employee: ['/employee', '/settings'], user: ['/settings'] } // Matching Paths in Regex const protectedRoutes = ['/admin', '/employee', '/settings']; const publicRoutes = ['/login', '/register'] export function middleware(req: NextRequest) { const session = req.cookies.get('next-auth.session-token'); const isProtectedRoute = protectedRoutes.some((path) => req.nextUrl.pathname.startsWith(path)) if (!session && isProtectedRoute) { return NextResponse.redirect(new URL('/login', req.url)) } // User roles and paths - Database driven const role = 'employee'; //Should come from database if (roles[role] && roles[role].some((path) => req.nextUrl.pathname.startsWith(path))) { return NextResponse.next(); } return NextResponse.redirect(new URL('/', req.url)); } // See "Matching Paths" below to learn more export const config = { matcher: ['/employee/:path*', '/admin/:path*', '/settings/:path*'] } """ * **Explanation:** * This uses Next.js middleware. * It redirects unauthenticated users to the login page. * Determines user role and compares to allowed routes on the "roles" object. * **Important:** Access user roles from a database or secure store. *Do not* hardcode roles in middleware. This demonstration is over-simplified for illustrative purposes. ### 2.4 Anti-Patterns * **Storing sensitive information in cookies:** Avoid storing sensitive data (e.g., passwords, API keys) in cookies. If you must store sensitive data in cookies, encrypt it properly and set appropriate flags (e.g., "HttpOnly", "Secure", "SameSite"). * **Using weak password hashing algorithms:** Always use strong and modern password hashing algorithms like bcrypt, Argon2, or scrypt. * **Implementing "remember me" functionality insecurely:** Use secure tokens with expiration dates and rotate them regularly. Do not simply store usernames and passwords in cookies. * **CORS misconfiguration:** Incorrect CORS settings can expose your API to cross-site request forgery attacks. Carefully configure allowed origins. ## 3. Data Protection ### 3.1 Standard * **Do This:** Encrypt sensitive data at rest and in transit. Use HTTPS for all communication. Store encryption keys securely. Implement proper data masking techniques if showing data in UI. * **Don't Do This:** Store sensitive data in plain text. Use outdated or weak encryption algorithms. Expose sensitive data in logs or error messages. ### 3.2 Why * **Security:** Protects data from unauthorized access and disclosure. Complies with regulations. * **Maintainability:** Centralized encryption and key management simplifies security maintenance. * **Compliance:** Many regulations (e.g., GDPR, HIPAA) require data protection measures. ### 3.3 Code Examples #### 3.3.1 Data Encryption with "crypto" Module (Node.js API Route) """typescript // pages/api/encrypt.ts import { NextApiRequest, NextApiResponse } from 'next'; import crypto from 'crypto'; const algorithm = 'aes-256-cbc'; // Use a strong encryption algorithm const key = crypto.randomBytes(32); // 32 bytes = 256 bits const iv = crypto.randomBytes(16); // 16 bytes = 128 bits export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).end(); } const { data } = req.body; if (!data) { return res.status(400).json({ error: 'Data is required.' }); } // Encryption const cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv); let encrypted = cipher.update(data); encrypted = Buffer.concat([encrypted, cipher.final()]); const encryptedData = { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') }; // Decryption (for demonstration purposes within the same endpoint) const decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), Buffer.from(encryptedData.iv, 'hex')); let decrypted = decipher.update(Buffer.from(encryptedData.encryptedData, 'hex')); decrypted = Buffer.concat([decrypted, decipher.final()]); // Securely store the key and IV. Do *NOT* hardcode or expose them. // In a real-world scenario, you'd store these in a secure key vault console.log('Encryption Key (keep secret!):', key.toString('hex')); console.log('Initialization Vector (keep secret!):', iv.toString('hex')); res.status(200).json({ encryptedData, decryptedData: decrypted.toString() }); } """ * **Explanation:** * Uses the "crypto" module for symmetric encryption. Specifically, "aes-256-cbc" is recommended for its strength. Other modern and well-vetted algorithms are also appropriate. * Generates a random encryption key and initialization vector (IV) for each encryption operation. *Important:* **Never** hardcode encryption keys or IVs. * Demonstrates encryption and decryption. * **Critical:** In a real application, store the "key" and "iv" securely (e.g., using Vercel's Environment Variables feature with encryption, or a dedicated key management system like HashiCorp Vault). *Never* expose them in your code or logs. #### 3.3.2 HTTPS Enforcement on Vercel Vercel automatically provides and manages SSL certificates for your domains. Enforce HTTPS by: 1. **Verifying domain configuration:** Ensure your domain is correctly configured in your Vercel project and that the SSL certificate is valid. 2. **Using "strict-transport-security" header:** This header tells browsers to only access your site over HTTPS. Configure this in your "next.config.js" (or similar) file. #### 3.3.3 next.config.js Configuration """javascript // next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, async headers() { return [ { source: '/(.*)', headers: [ { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload', }, ], }, ]; }, } module.exports = nextConfig """ * **Explanation:** * The configuration above will automatically set the Strict-Transport-Security (HSTS) header for all requests, ensuring that browsers only access your site over HTTPS. ### 3.4 Anti-Patterns * **Hardcoding encryption keys:** Never hardcode encryption keys in your code. Store them securely in environment variables or a key management system. * **Using ECB mode encryption:** ECB mode is vulnerable to pattern analysis. Use CBC, GCM, or other secure modes. * **Logging sensitive data:** Redact sensitive data from logs and error messages. * **Not rotating encryption keys:** Regularly rotate encryption keys to minimize the impact of a potential key compromise. ## 4. Dependency Management ### 4.1 Standard * **Do This:** Keep your dependencies up to date with the latest security patches. Use a dependency management tool (e.g., npm, yarn, pnpm) to manage your dependencies and their versions. Use a tool like Snyk, Dependabot, or Vercel's built-in vulnerability detection to identify and remediate vulnerabilities in your dependencies. * **Don't Do This:** Use outdated dependencies with known vulnerabilities. Ignore security alerts from dependency scanning tools. Install dependencies from untrusted sources. ### 4.2 Why * **Security:** Prevents exploitation of known vulnerabilities in dependencies. * **Maintainability:** Up-to-date dependencies often include bug fixes and performance improvements. * **Compliance:** Many regulations require patching known vulnerabilities. ### 4.3 Code Examples #### 4.3.1 Using Dependabot on GitHub (Recommended) 1. **Enable Dependabot:** If your Vercel project is linked to a GitHub repository, enable Dependabot version updates and security updates in your repository settings (Security -> Dependabot). 2. **Review and merge pull requests:** Dependabot will automatically create pull requests to update your dependencies. Review these pull requests carefully and merge them to keep your dependencies up to date. #### 4.3.2 Using Snyk CLI locally 1. **Install Snyk:** """bash npm install -g snyk """ 2. **Authenticate Snyk:** """bash snyk auth """ 3. **Test your project:** """bash snyk test """ This command scans your project for vulnerabilities and provides remediation advice #### 4.3.3 Using npm audit """bash npm audit """ This command will scan your package-lock.json or package.json file for known vulnerabilities. * Update vulnerable packages by : """bash npm audit fix """ ### 4.4 Anti-Patterns * **Ignoring Dependabot alerts:** Treat Dependabot alerts seriously and address them promptly. * **Disabling dependency scanning:** Never disable dependency scanning tools. * **Installing dependencies globally:** Avoid installing dependencies globally, as this can lead to conflicts and inconsistencies. * **Using wildcard version ranges:** Avoid using overly broad version ranges (e.g., "^1.0.0") in your "package.json" file, as this can introduce breaking changes unexpectedly. Use more specific version ranges (e.g., "~1.0.0" or "1.0.0"). * **Committing "node_modules":** Under no circumstance commit the "node_modules" directory to version control. ## 5. Error Handling and Logging ### 5.1 Standard * **Do This:** Implement proper error handling and logging to capture and analyze errors. Log sufficient information to diagnose and resolve issues, but avoid logging sensitive data. Implement centralized logging to enable efficient analysis. * **Don't Do This:** Expose detailed error messages to users. Log sensitive data. Ignore errors. ### 5.2 Why * **Security:** Helps identify and respond to security incidents. Prevents attackers from gathering information about your system through error messages. * **Maintainability:** Enables quick diagnosis and resolution of issues. * **Performance:** Helps identify performance bottlenecks. ### 5.3 Code Examples #### 5.3.1 Centralized Logging with Vercel Analytics Vercel Analytics provides built-in error tracking and logging capabilities. Use it to: 1. **Track errors in production:** Vercel Analytics automatically captures errors that occur in your application. 2. **Analyze error trends:** Use the Vercel Analytics dashboard to identify common error patterns and prioritize remediation efforts. 3. **Correlate errors with deploys:** Vercel Analytics allows you to correlate errors with specific deployments, making it easier to identify the root cause of issues. #### 5.3.2 Custom Error Logging (Next.js API Route) """typescript // pages/api/example.ts import { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { // Your code here if (Math.random() < 0.5) { throw new Error('Simulated error'); } res.status(200).json({ message: 'Success' }); } catch (error: any) { console.error('API Error:', error); // Log the error to console (for Vercel logs) // In a production environment, you'd send this error to a central logging service // Example: sentry.captureException(error); res.status(500).json({ error: 'Internal server error' }); // Generic error message } } """ * **Explanation:** * The code includes a "try...catch" block to handle errors gracefully. * It logs the error to the console using "console.error()". Vercel automatically captures these console logs. * It sends a generic error message to the client to avoid exposing sensitive information. * **Important:** In a production environment, integrate with a dedicated logging service like Sentry, Datadog, or similar. #### 5.3.4 Data masking functions for protecting sensitive information """typescript function maskEmail(email: string): string { const [username, domain] = email.split('@'); const maskedUsername = username.slice(0, 2) + '*'.repeat(username.length - 2); return "${maskedUsername}@${domain}"; } function maskPhoneNumber(phoneNumber: string): string { return phoneNumber.replace(/(\d{3})\d{3}(\d{4})/, '$1***$2'); } """ * **Explanation** * The "maskEmail" function keeps the first two characters of the username and replaces the rest with asterisks. * The "maskPhoneNumber" function keeps the first three and last four digits, replacing the middle digits with asterisks. ### 5.4 Anti-Patterns * **Exposing stack traces to users:** Never expose stack traces or other detailed error information to users. This can reveal sensitive information about your system. * **Logging passwords or API keys:** Never log sensitive data like passwords or API keys. * **Ignoring unhandled exceptions:** Always handle unhandled exceptions to prevent your application from crashing. Implement global error handlers to catch unexpected errors. * **Over-logging:** Avoid logging excessively, as this can impact performance and storage costs. ## 6. Environment Variables and Secrets Management ### 6.1 Standard * **Do This:** Store sensitive information (e.g., API keys, database passwords) in environment variables. Encrypt environment variables if required. Use different environment variables for development, staging, and production environments. Use Vercel's built in secrets management. * **Don't Do This:** Hardcode secrets in your code. Commit secrets to version control. Use the same secrets for all environments. ### 6.2 Why * **Security:** Protects sensitive data from unauthorized access. * **Maintainability:** Simplifies configuration management. * **Compliance:** Many regulations require protecting secrets. ### 6.3 Code Examples #### 6.3.1 Using Vercel Environment Variables 1. **Set environment variables in the Vercel dashboard:** Go to your Vercel project settings and add environment variables under the "Environment Variables" section. 2. **Access environment variables in your code:** Use "process.env.VARIABLE_NAME" to access environment variables in your code. """typescript // pages/api/data.ts import { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const apiKey = process.env.API_KEY; if (!apiKey) { console.error('API_KEY environment variable is not set.'); return res.status(500).json({ error: 'Internal server error' }); } // Use the API key to fetch data // ... } """ #### 6.3.2 Encrypting Environment Variables Vercel offers the ability to encrypt environment variables within the Vercel dashboard for enhanced security. ### 6.4 Anti-Patterns * **Committing ".env" files:** Never commit ".env" files (containing local environment variables) to version control. Add ".env" to your ".gitignore" file. Vercel's environment variables are set via the dashboard and do not use ".env" files in production. * **Using default secrets:** Avoid using default secrets provided by third-party services. Change them to strong, randomly generated values. * **Exposing secrets in client-side code:** Never expose secrets in client-side JavaScript code. Use API routes or serverless functions to access secrets securely. ## 7. Denial-of-Service (DoS) Protection ### 7.1 Standard *Implement rate limiting to protect your application from being overwhelmed by excessive requests. *Implement input validation and sanitization to prevent attackers from injecting malicious data that could consume excessive resources. *Utilize Vercel's built-in caching mechanisms and CDN to reduce the load on your origin servers. *Implement connection limits to limit the number of concurrent connections from a single IP address or user.* ### 7.2 Why * Prevents attackers from overwhelming the application with malicious requests. Reduces service unavailabilty. Protects from large scale attacks. ### 7.3 Code Examples #### 7.3.1 Rate Limiting Implementation * This can be implemented in Node.js environment using libraries like "express-rate-limit" or "limiter". Rate Limiting can also be implemented at a reverse proxy level. """typescript // middleware.ts import { NextResponse } from 'next/server' import { RateLimiter } from 'limiter' import type { NextRequest } from 'next/server' const limiter = new RateLimiter({tokensPerInterval: 50, interval: 'minute'}) export async function middleware(req: NextRequest) { const remaining = await limiter.removeTokens(1) if (remaining < 0) { return new NextResponse( JSON.stringify({ message: 'Too Many Requests' }), { status: 429, headers: { 'content-type': 'application/json', }, } ) } return NextResponse.next(); } // See "Matching Paths" below to learn more export const config = { matcher: ['/api/:path*'] } """ * **Explanation:** * This uses Next.js middleware. * "tokensPerInterval" is the amount of calls allowed per "interval". In this case 50 per minute. ### 7.4 Anti-Patterns * Not implementing DDoS protection. * Not using a DDoS prevention service. * Ignoring suspicious traffic patterns. ## 8. Security Audits and Penetration Testing ### 8.1 Standard * Conduct regular security audits of your code and infrastructure to identify potential vulnerabilities. * Perform penetration testing to simulate real-world attacks and identify weaknesses in your application. * Engage external security experts to provide independent assessments and recommendations. * Establish a process for addressing and remediating identified vulnerabilities in a timely manner. ### 8.2 Why * Proactively identifies and address security vulnerabilities. Provides a high-level of code and application security * Helps to prevent security incidents and data breaches. ### 8.3 Code Examples * Tools such as OWASP ZAP or Burp Suite can perform automated testing of web application security. Regular code reviews utilizing the above security standards are also considered part of auditing. ### 8.4 Anti-Patterns * No use of Security Audits. * No code reviews. * Not using penetration testing. By implementing these security best practices, you can significantly reduce your application's risk profile and protect your data and users. Regularly review and update these standards to keep pace with the evolving threat landscape.
# Core Architecture Standards for Vercel This document outlines the core architectural standards for developing applications on Vercel. It focuses on fundamental architectural patterns, optimal project structure, and organizational principles to ensure maintainability, performance, and security. These standards are tailored to the Vercel platform and promote the use of modern approaches and patterns. ## 1. Architectural Patterns Selecting the right architectural pattern is crucial for building scalable and maintainable Vercel applications. ### 1.1. Serverless Functions (API Routes) Leverage serverless functions for handling API endpoints and backend logic. Vercel's serverless functions allow you to execute code without managing servers, scaling automatically and minimizing operational overhead. **Do This:** * Use serverless functions for handling API requests, database interactions, and background tasks. **Don't Do This:** * Avoid running long-running processes directly within serverless functions. Use queues or background jobs for computationally intensive tasks. **Why:** Serverless functions ensure scalability, reduce costs by only charging for actual usage, and simplify deployment management. **Code Example (Next.js API Route):** """javascript // pages/api/hello.js export default async function handler(req, res) { if (req.method === 'GET') { res.status(200).json({ message: 'Hello from Vercel!' }); } else { res.status(405).json({ message: 'Method Not Allowed' }); } } """ **Anti-Pattern:** Placing heavy computation directly inside a serverless function without considering timeouts or performance implications. ### 1.2. Jamstack Architecture Jamstack (JavaScript, APIs, and Markup) is an architectural approach that emphasizes pre-rendering, decoupling, and leveraging edge caching. **Do This:** * Pre-render as much content as possible during build time using Static Site Generation (SSG) or Incremental Static Regeneration (ISR). * Use a Content Delivery Network (CDN) for serving static assets. Vercel's Edge Network is ideal for this. * Decouple the frontend from the backend using APIs, allowing for greater flexibility and scalability. **Don't Do This:** * Rely on server-side rendering (SSR) for content that can be pre-rendered. * Bypass the CDN and directly serve static assets from the origin server. **Why:** Jamstack enhances performance, improves security by minimizing the attack surface, and simplifies deployments. **Code Example (Next.js SSG):** """javascript // pages/index.js export async function getStaticProps() { // Fetch data from an API or CMS const data = await fetchData(); return { props: { data, }, }; } export default function Home({ data }) { return ( <h1>{data.title}</h1> <p>{data.content}</p> ); } """ **Code Example (Next.js ISR):** """javascript // pages/blog/[slug].js export async function getStaticPaths() { // Get all possible paths based on external data const posts = await fetchPosts(); const paths = posts.map((post) => ({ params: { slug: post.slug }, })); return { paths, fallback: 'blocking', // or true for incremental generation }; } export async function getStaticProps({ params }) { // Fetch data for the specific slug const post = await fetchPost(params.slug); return { props: { post, }, revalidate: 60, // Revalidate every 60 seconds }; } export default function Post({ post }) { return ( <h1>{post.title}</h1> <p>{post.content}</p> ); } """ **Anti-Pattern:** Using SSR for content that is rarely updated, leading to unnecessary server load and slower page loads. ### 1.3. Microservices (Polyrepo vs. Monorepo using Vercel Projects) When the application grows in complexity, consider a microservices architecture which Vercel can support effectively through multiple "Vercel Projects" pointing at different repositories. **Do This:** * If choosing microservices, design services with clear boundaries and single responsibilities. * Consider the trade-offs between a polyrepo (multiple repositories, one per service) and a monorepo (single repository for all services) approach. Vercel Projects work equally well for either, but the organization and deployment strategies differ. * Use a service mesh or API gateway for managing communication between services. * Set up separate Vercel projects for each microservice to manage deployments and scaling independently. **Don't Do This:** * Create overly granular microservices that introduce unnecessary complexity. * Share database schemas or business logic between services without careful consideration. **Why:** Microservices improve scalability, fault isolation, and team autonomy. Vercel's project structure allows for easy management of multiple services. **Polyrepo Example:** * "repo-auth-service" Vercel Project -> connects to auth-service repo * "repo-user-service" Vercel Project -> connects to user-service repo * "repo-product-service" Vercel Project -> connects to product-service repo **Monorepo Example:** * "main-repo" Vercel Project (configure the settings to only deploy changed directories) **Anti-Pattern:** Creating a "distributed monolith" where services are tightly coupled and dependent on each other. ### 1.4 Edge Functions Utilize Vercel Edge Functions for tasks that require low latency and high performance, like personalization and geolocation. **Do This:** * Implement Edge Functions for tasks like A/B testing, authentication, and request rewriting, leveraging geographical proximity to the user. * Cache responses at the edge to reduce latency and improve performance. **Don't Do This:** * Execute complex business logic or database operations within Edge Functions due to limitations on execution time and resources. * Overuse Edge Functions for tasks that can be efficiently handled by serverless functions. **Why:** Edge Functions provide low latency, improved performance, and scalability at the edge of the network. **Code Example (Edge Function):** """typescript // middleware.ts (Vercel's Edge Middleware) import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const country = request.geo?.country || 'US' if (country === 'DE') { return NextResponse.rewrite(new URL('/de/homepage', request.url)) } return NextResponse.next() } export const config = { matcher: '/', } """ **Anti-Pattern:** Using Edge Functions for operations that require significant computation or database access results in slow performance and potential timeouts. ## 2. Project Structure and Organization A well-defined project structure is essential for maintainability and collaboration. ### 2.1. Monorepo vs. Polyrepo Choose a project structure that aligns with your organization's size, complexity, and development workflow. **Do This:** * For small to medium-sized projects, start with a monorepo for simplicity using tools like Turborepo or Nx. * For large, complex applications with multiple teams, consider a polyrepo structure for greater autonomy and scalability. **Don't Do This:** * Switch between monorepo and polyrepo without carefully considering the implications on tooling, deployment, and collaboration. * Create a deeply nested directory structure within the repository. **Why:** A well-organized project structure improves code discoverability, simplifies dependency management, and promotes code reuse. **Monorepo Example (using Turborepo):** """ my-monorepo/ apps/ web/ # Next.js frontend docs/ # Documentation website packages/ ui/ # UI component library utils/ # Utility functions turbo.json # Turborepo configuration package.json # Root package.json """ **Anti-Pattern:** A disorganized repository structure that makes it difficult to find and understand code. ### 2.2. Directory Structure for Next.js Projects Organize Next.js projects using a structure that promotes separation of concerns and maintainability. **Do This:** * Use the "pages" directory for defining routes and API endpoints. * Create a "components" directory for reusable UI components. * Store utility functions and helper modules in a "utils" or "lib" directory. * Use an "assets" directory for static assets like images and fonts. * Create a "styles" directory for CSS modules, global stylesheets, and theme definitions. **Don't Do This:** * Place all code within the "pages" directory, leading to a monolithic structure. * Mix components, utilities, and styles in the same directory. **Why:** A standardized directory structure enables clear separation of concerns, simplifies navigation, and promotes code reuse. **Code Example (Directory Structure):** """ my-next-app/ pages/ api/ hello.js # API endpoint index.js # Home page about.js # About page components/ Button.js # Reusable button component Header.js # Header component utils/ api.js # API client functions formatDate.js # Date formatting utility assets/ logo.png # Logo image styles/ globals.css # Global styles Button.module.css # CSS Modules for Button component """ **Anti-Pattern:** A flat directory structure that mixes different types of files, making it difficult and time-consuming to find specific code. ### 2.3. Component-Based Architecture Design the application using reusable and composable components. **Do This:** * Break down the UI into smaller components with single responsibilities. * Use prop types or TypeScript for defining component interfaces. * Create a component library for reusable UI elements. **Don't Do This:** * Create large, monolithic components that are difficult to maintain and reuse. * Duplicate code across multiple components. **Why:** A component-based architecture promotes code reuse, simplifies maintenance, and improves testability. **Code Example (React Component):** """jsx // components/Button.js import React from 'react'; import styles from './Button.module.css'; import PropTypes from 'prop-types'; function Button({ children, onClick, className }) { return ( {children} ); } Button.propTypes = { children: PropTypes.node.isRequired, onClick: PropTypes.func, className: PropTypes.string, }; export default Button; """ **Anti-Pattern:** Duplicating code across multiple components instead of creating a reusable component. ## 3. Code Organization Principles ### 3.1. Separation of Concerns Divide the code into distinct sections, each addressing a separate concern. **Do This:** * Separate concerns for UI rendering, data fetching, business logic, and data storage. * Use custom hooks to encapsulate reusable logic. **Don't Do This:** * Mix UI rendering, data fetching, and business logic in the same component. **Why:** Separation of concerns improves code maintainability, testability, and readability. **Code Example (Custom Hook):** """javascript // hooks/useFetch.js import { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchData() { try { const response = await fetch(url); const json = await response.json(); setData(json); } catch (e) { setError(e); } finally { setLoading(false); } } fetchData(); }, [url]); return { data, loading, error }; } export default useFetch; // pages/index.js import useFetch from '../hooks/useFetch'; function Home() { const { data, loading, error } = useFetch('/api/data'); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; return ( <h1>Data from API</h1> <pre>{JSON.stringify(data, null, 2)}</pre> ); } export default Home; """ **Anti-Pattern:** A component that performs UI rendering, data fetching, and data manipulation, making it difficult to understand and maintain. ### 3.2. Single Responsibility Principle (SRP) Ensure that each class, function, or component has only one reason to change. **Do This:** * Design functions and components that perform a single, well-defined task. * Avoid creating functions or classes that are responsible for multiple unrelated tasks. **Don't Do This:** * Create a utility function that can format dates, validate emails, and trim strings, violating SRP. **Why:** SRP improves code clarity, testability, and reduces the risk of introducing bugs when modifying code. **Code Example (SRP Compliant Function):** """javascript // utils/formatDate.js function formatDate(date) { return new Date(date).toLocaleDateString(); } export default formatDate; // utils/validateEmail.js function validateEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); } export default validateEmail; """ **Anti-Pattern:** A lengthy function that could be broken down into smaller, more manageable functions, violating SRP. ### 3.3. Convention over Configuration Adopt a common set of conventions for naming, directory structure, and coding style. Vercel / Next.js encourages this, but you can codify more specific rules. **Do This:** * Use consistent naming conventions for variables, functions, and components. * Follow a standardized directory structure for organizing project files. * Use a configuration management system to centralize and manage application settings. **Don't Do This:** * Inconsistent naming conventions that make it difficult to understand the purpose of a variable or function. **Why:** Convention over configuration reduces cognitive load, improves code consistency, and simplifies onboarding for new developers. **Technology-Specific Details:** * Leverage Vercel's environment variables for configuring different deployment environments. * Use Vercel's analytics and logging to monitor application performance and identify potential issues. * Use Next.js's built-in features for routing, API endpoints, and data fetching. By adhering to these core architectural standards, you can build robust, scalable, and maintainable applications on Vercel. These standards are intended to provide a foundation for creating high-quality code that is easy to understand, test, and extend.
# Component Design Standards for Vercel This document outlines the component design standards for Vercel projects. These standards are designed to promote reusable, maintainable, and performant code, taking advantage of Vercel's features and ecosystem. The goal is to provide clear guidelines that enhance code quality, team collaboration, and overall project success. ## 1. Component Architecture and Organization ### Standard 1.1: Atomic Design Principles **Do This:** Structure components based on Atomic Design principles: Atoms, Molecules, Organisms, Templates, and Pages. **Don't Do This:** Create monolithic components that handle multiple responsibilities. **Why:** Atomic Design promotes modularity, reusability, and testability. **Example:** """jsx // Atoms // Button.jsx const Button = ({ children, onClick }) => ( <button onClick={onClick}>{children}</button> ); export default Button; // Molecules // SearchBar.jsx import Button from './Button'; const SearchBar = ({ onSearch }) => ( <div> <input type="text" placeholder="Search..." /> <Button onClick={onSearch}>Search</Button> </div> ); export default SearchBar; // Organisms // Header.jsx import SearchBar from './SearchBar'; const Header = () => ( <header> <h1>My App</h1> <SearchBar onSearch={() => console.log('Searching...')} /> </header> ); export default Header; // Template // BasicLayout.jsx import Header from './Header'; import Footer from './Footer' const BasicLayout = ({ children }) => ( <> <Header /> <main>{children}</main> <Footer /> </> ); export default BasicLayout; // Pages // HomePage.jsx import BasicLayout from './BasicLayout'; const HomePage = () => ( <BasicLayout> {/* Page content here */} <h1>Welcome to the Home Page</h1> </BasicLayout> ); export default HomePage; """ ### Standard 1.2: Component Directory Structure **Do This:** Organize components in a clear, hierarchical directory structure. A common pattern is "components/{ComponentName}/{ComponentName}.jsx", "components/{ComponentName}/index.jsx", potentially with accompanying test files. **Don't Do This:** Scatter components randomly throughout the project. **Why:** A well-defined directory structure improves navigation and maintainability. **Example:** """ src/ └── components/ ├── Button/ │ ├── Button.jsx │ └── Button.module.css (Optional, for CSS Modules) ├── SearchBar/ │ ├── SearchBar.jsx │ └── SearchBar.module.css ├── Header/ │ ├── Header.jsx │ └── Header.module.css └── index.js (optional: exports all components) """ "src/components/index.js" (Optional): """javascript export { default as Button } from './Button/Button'; export { default as SearchBar } from './SearchBar/SearchBar'; export { default as Header } from './Header/Header'; """ ### Standard 1.3: Container vs. Presentational Components **Do This:** Separate components into "container" components (data fetching, state management) and "presentational" components (UI rendering). **Don't Do This:** Mix data fetching and UI rendering logic within a single component. **Why:** Separation of concerns makes components more reusable and testable. In the context of Vercel and Next.js, this can improve performance by controlling where data fetching happens (server vs client). **Example:** """jsx // Container Component (fetches data) - e.g., pages/blog/[id].js or a dedicated component // Client-side rendering with useEffect example import { useState, useEffect } from 'react'; import BlogPost from '../../components/BlogPost/BlogPost'; // Presentational Component const BlogPostContainer = ({ id }) => { const [post, setPost] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchPost = async () => { // Use relative URL for API Route const res = await fetch("/api/blog/${id}"); if (!res.ok) { throw new Error("Failed to fetch post with id ${id}"); } const data = await res.json(); setPost(data); setLoading(false); }; fetchPost(); }, [id]); if (loading) { return <p>Loading...</p>; } if (!post) { return <p>Post not found.</p>; } return <BlogPost post={post} />; }; // Server-side rendering approach using getStaticProps export async function getStaticProps({ params }) { const { id } = params; // Direct API call to your backend or CMS - example const res = await fetch("https://your-cms.com/api/blog/${id}"); const post = await res.json(); return { props: { post, }, }; } export async function getStaticPaths() { // Fetch a list of all post IDs // Direct API call to your backend or CMS - example const res = await fetch("https://your-cms.com/api/blog"); // fetches all posts const posts = await res.json(); const paths = posts.map((post) => ({ params: { id: post.id.toString() }, })); return { paths, fallback: false, // or 'blocking' }; } export default BlogPostContainer; // Presentational Component (renders UI) - BlogPost.jsx const BlogPost = ({ post }) => ( <div> <h2>{post.title}</h2> <p>{post.content}</p> </div> ); export default BlogPost; """ ## 2. Component Implementation ### Standard 2.1: Functional Components with Hooks **Do This:** Use functional components with React Hooks for state management and side effects. **Don't Do This:** Rely heavily on class-based components, especially for new code. **Why:** Functional components with Hooks are more concise, readable, and easier to test. **Example:** """jsx import { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default Counter; """ ### Standard 2.2: Prop Types and TypeScript **Do This:** Use prop types or TypeScript to define the expected types of component props. TypeScript is strongly preferred. **Don't Do This:** Define components without specifying the types of props. **Why:** Type checking helps catch errors early and improves code maintainability. In a Vercel & Next.js context, TypeScript helps ensure API responses are correctly handled in your components, avoiding runtime errors. **Example (TypeScript):** """tsx interface ButtonProps { label: string; onClick: () => void; disabled?: boolean; // Optional prop } const Button: React.FC<ButtonProps> = ({ label, onClick, disabled }) => ( <button onClick={onClick} disabled={disabled}> {label} </button> ); export default Button; """ ### Standard 2.3: Controlled vs. Uncontrolled Components **Do This:** Choose between controlled and uncontrolled components based on your needs. Generally, prefer controlled components when you need to directly manipulate the value, and uncontrolled components when you just need to get the value on submit. **Don't Do This:** Use uncontrolled components without a clear understanding of their implications for state management. **Why:** Understanding controlled and uncontrolled components is crucial for managing form state effectively. **Example (Controlled):** """jsx import { useState } from 'react'; const Input = () => { const [value, setValue] = useState(''); return ( <input type="text" value={value} onChange={(e) => setValue(e.target.value)} /> ); }; export default Input; """ **Example (Uncontrolled):** """jsx const Input = () => { const handleSubmit = (e) => { e.preventDefault(); const value = e.target.myInput.value; console.log(value); }; return ( <form onSubmit={handleSubmit}> <input type="text" name="myInput" /> <button type="submit">Submit</button> </form> ); }; export default Input; """ ### Standard 2.4: Handling Events **Do This:** Use arrow functions or "bind" to ensure the correct "this" context in event handlers. **Don't Do This:** Use inline functions in JSX without considering performance implications (re-renders). Memoize functions when necessary. **Why:** Correctly binding event handlers prevents unexpected behavior. Memoization optimizes performance by preventing unnecessary re-renders. **Example:** """jsx import { useState, useCallback } from 'react'; const MyComponent = () => { const [count, setCount] = useState(0); // Memoize the increment function const increment = useCallback(() => { setCount(prevCount => prevCount + 1); }, []); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); }; export default MyComponent; """ ### Standard 2.5: Key Property for Lists **Do This:** When rendering lists of elements, provide a unique "key" prop to each element. **Don't Do This:** Use array indices as keys unless the list is static and never changes. **Why:** Keys help React efficiently update the DOM when list items are added, removed, or reordered. Using indices as keys can lead to unexpected behavior and performance problems. **Example:** """jsx const MyList = ({ items }) => ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ); export default MyList; """ ## 3. Styling and CSS ### Standard 3.1: CSS Modules or Styled Components **Do This:** Use CSS Modules or Styled Components for styling components. TailwindCSS is also acceptable but should be used via "tailwind.config.js" and the "@apply" directive for component-specific styling (discouraging inline "className" strings) to maintain consistency. **Don't Do This:** Use global CSS styles that can conflict with other components. **Why:** CSS Modules and Styled Components provide scoping and prevent naming collisions, enhancing maintainability. Tailwind's "@apply" promotes reuse and reduces redundant configurations. **Example (CSS Modules):** """jsx // Button.module.css .button { background-color: blue; color: white; padding: 10px 20px; border: none; cursor: pointer; } // Button.jsx import styles from './Button.module.css'; const Button = ({ children, onClick }) => ( <button className={styles.button} onClick={onClick}> {children} </button> ); export default Button; """ **Example (Styled Components):** """jsx import styled from 'styled-components'; const StyledButton = styled.button" background-color: blue; color: white; padding: 10px 20px; border: none; cursor: pointer; "; const Button = ({ children, onClick }) => ( <StyledButton onClick={onClick}> {children} </StyledButton> ); export default Button; """ **Example (TailwindCSS with "@apply"):** """css /* Button.css (or Button.module.css) */ .button { @apply bg-blue-500 text-white py-2 px-4 rounded; } /* Button.jsx */ import './Button.css'; // or import styles from './Button.module.css'; const Button = ({ children, onClick }) => ( <button className="button" onClick={onClick}> {children} </button> ); export default Button; """ ### Standard 3.2: Responsive Design **Do This:** Implement responsive design using media queries or CSS-in-JS libraries like Styled Components that support media queries. Libraries like "react-responsive" or "useMediaQuery" hook can also be utilized. **Don't Do This:** Create layouts that are only suitable for a single screen size. **Why:** Responsive design ensures a consistent user experience across different devices. **Example (Styled Components with Media Queries):** """jsx import styled from 'styled-components'; const ResponsiveDiv = styled.div" width: 100%; @media (min-width: 768px) { width: 50%; } @media (min-width: 1200px) { width: 33.33%; } "; const MyComponent = () => ( <ResponsiveDiv> This div will adjust its width based on screen size. </ResponsiveDiv> ); export default MyComponent; """ ### Standard 3.3: Theming (for visual consistency) **Do This:** Implement theming using React Context or a dedicated theming library like "styled-components". CSS variables (custom properties) are a good alternative if broad browser support is required. **Don't Do This:** Hardcode colors, fonts, and other style values directly in components. **Why:** Theming allows for easy customization and maintains a consistent look and feel across the application. **Example (React Context):** """jsx // ThemeContext.jsx import { createContext, useState } from 'react'; export const ThemeContext = createContext(); export const ThemeProvider = ({ children }) => { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(theme === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); }; // Button.jsx import { useContext } from 'react'; import { ThemeContext } from './ThemeContext'; const Button = ({ children, onClick }) => { const { theme } = useContext(ThemeContext); const buttonStyle = { backgroundColor: theme === 'light' ? 'blue' : 'darkblue', color: 'white', padding: '10px 20px', border: 'none', cursor: 'pointer', }; return ( <button style={buttonStyle} onClick={onClick}> {children} </button> ); }; export default Button; // App.jsx import { ThemeProvider } from './ThemeContext'; import Button from './Button'; const App = () => ( <ThemeProvider> <Button onClick={() => console.log('Clicked!')}>Click Me</Button> </ThemeProvider> ); export default App; """ ## 4. Data Fetching and API Communication (Vercel Specific) ### Standard 4.1: "getStaticProps" and "getServerSideProps" **Do This:** Use "getStaticProps" for data that is known at build time (e.g., content from a CMS). Use "getServerSideProps" for data that needs to be fetched on every request (e.g., user-specific data). Utilize Incremental Static Regeneration (ISR) with "revalidate" to balance static performance with near real-time updates. **Don't Do This:** Fetch data on the client-side if it can be fetched during build or server-side rendering, whenever SEO is a concern. This includes avoiding the "useEffect" hook for initial data population when possible. **Why:** Using "getStaticProps" and "getServerSideProps" optimizes performance and SEO in Vercel. ISR allows you to update static pages efficiently while maintaining optimal performance. **Example ("getStaticProps" with ISR):** """jsx export async function getStaticProps() { // Fetch data from an API const res = await fetch('https://.../posts'); const posts = await res.json(); return { props: { posts, }, revalidate: 60, // Regenerate the page every 60 seconds }; } const PostList = ({ posts }) => ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); export default PostList; """ **Example ("getServerSideProps"):** """jsx export async function getServerSideProps(context) { // Fetch data on every request const res = await fetch('https://.../user-data'); const userData = await res.json(); return { props: { userData, }, }; } const UserProfile = ({ userData }) => ( <div> <h1>{userData.name}</h1> <p>{userData.email}</p> </div> ); export default UserProfile; """ ### Standard 4.2: Vercel Edge Functions **Do This:** Utilize Vercel Edge Functions for tasks that require low latency and proximity to users (e.g., A/B testing, personalization). Place logic closer to the user when possible. **Don't Do This:** Use Edge Functions for long-running tasks or tasks that require access to a database that is not optimized for edge deployment. **Why:** Edge Functions provide low-latency execution and can improve the performance of geographically distributed applications. **Example:** """javascript // vercel.json { "routes": [ { "src": "/api/hello", "dest": "/api/hello.js" } ] } // api/hello.js (Edge Function) export const config = { runtime: 'edge', }; export default async function handler(request, context) { return new Response("Hello, from the edge!", { status: 200, headers: { 'content-type': 'text/plain', }, }); } """ ### Standard 4.3: API Routes **Do This:** Use Next.js API routes for backend logic that needs to run server-side within your Vercel application. Use them for form submissions, server-side data processing, or interacting with databases. Ensure proper error handling and validation. **Don't Do This:** Implement complex backend logic directly in client-side components. Handle API requests and data transformations exclusively client-side when it creates security risks client side. **Why:** API routes provide a secure and scalable way to handle backend logic in your Vercel application. **Example:** """javascript // pages/api/submit.js export default async function handler(req, res) { if (req.method === 'POST') { const data = req.body; // Validate data if (!data.name || !data.email) { return res.status(400).json({ error: 'Name and email are required' }); } // Process data (e.g., save to database) try { // Example: Simulate saving to a database console.log('Data received:', data); res.status(200).json({ message: 'Form submitted successfully' }); } catch (error) { console.error('Error processing data:', error); res.status(500).json({ error: 'Failed to process data' }); } } else { res.status(405).json({ error: 'Method not allowed' }); } } """ ### Standard 4.4: Error Handling **Do THIS:** Implement robust error handling in API routes and data fetching logic. Return meaningful error messages to the client. Use centralized error logging (e.g. Sentry) to track errors in production. **Don't Do THIS:** Expose sensitive information in error messages. Fail silently without logging errors. **Why:** Proper error handling helps prevent application crashes and provides valuable insights into potential issues. **Example:** """javascript // pages/api/data.js import { captureException } from '@sentry/nextjs'; // Assuming you are using Sentry export default async function handler(req, res) { try { // Simulate an error throw new Error('Failed to fetch data'); const data = await fetchData(); res.status(200).json(data); } catch (error) { console.error('Error fetching data:', error); captureException(error); // Log error to Sentry res.status(500).json({ error: 'Failed to fetch data. Please try again later.' }); // Generic error message to client. } } """ ## 5. Component Testing ### Standard 5.1: Unit Tests **Do This:** Write unit tests for individual components to ensure they function as expected. Use testing frameworks like Jest and React Testing Library. **Don't Do This:** Skip unit tests for components, especially complex ones. Rely solely on end-to-end tests. **Why:** Unit tests provide fast feedback and help catch bugs early in the development process. **Example:** """jsx // Button.test.jsx import { render, screen, fireEvent } from '@testing-library/react'; import Button from './Button'; test('renders a button with the correct label', () => { render(<Button label="Click Me" />); const buttonElement = screen.getByText('Click Me'); expect(buttonElement).toBeInTheDocument(); }); test('calls the onClick handler when clicked', () => { const onClick = jest.fn(); render(<Button label="Click Me" onClick={onClick} />); const buttonElement = screen.getByText('Click Me'); fireEvent.click(buttonElement); expect(onClick).toHaveBeenCalledTimes(1); }); """ ### Standard 5.2: Integration Tests **Do This:** Write integration tests to ensure that components work together correctly. **Don't Do This:** Test components in isolation without verifying their interactions with other components. **Why:** Integration tests verify that the different parts of the application work together as expected. ### Standard 5.3: End-to-End Tests **Do This:** Use end-to-end (E2E) testing frameworks like Cypress or Playwright to test the entire application flow, simulating real user interactions. Vercel integrates seamlessly with Cypress for automated testing during deployment. **Don't Do This:** Rely solely on manual testing. Deploy code without any automated E2E testing. **Why:** E2E tests verify that the application works correctly from the user's perspective and help catch integration issues. ## 6. Component Documentation ### Standard 6.1: JSDoc or TypeScript Documentation **Do This:** Use JSDoc comments or TypeScript to document components, their props, and their behavior. **Don't Do This:** Leave components undocumented. Write insufficient documentation that does not explain how a component should be used. **Why:** Documentation makes components easier to understand and use by other developers. **Example (JSDoc):** """jsx /** * A reusable button component. * * @param {string} label - The text to display on the button. * @param {function} onClick - The function to call when the button is clicked. * @param {boolean} [disabled=false] - Whether the button is disabled. */ const Button = ({ label, onClick, disabled }) => ( <button onClick={onClick} disabled={disabled}> {label} </button> ); export default Button; """ **Example (TypeScript):** """tsx interface ButtonProps { /** The text to display on the button. */ label: string; /** The function to call when the button is clicked. */ onClick: () => void; /** Whether the button is disabled. Defaults to false. */ disabled?: boolean; } /** A reusable button component. */ const Button: React.FC<ButtonProps> = ({ label, onClick, disabled }) => ( <button onClick={onClick} disabled={disabled}> {label} </button> ); export default Button; """ ### Standard 6.2: Component Storybook **Do This:** Use Storybook to create a component library with interactive examples of each component. **Don't Do This:** Develop components in isolation without a visual representation of their usage. **Why:** Storybook provides a visual way to browse and test components, making it easier to maintain a consistent UI and ensure components are reusable. Also, it supports automated visual regression tests. ## 7. Accessibility (A11Y) ### Standard 7.1: ARIA Attributes **Do This:** Use ARIA attributes to provide semantic information to assistive technologies, such as screen readers. Verify accessibility using tools like Axe. **Don't Do This:** Ignore accessibility considerations. Create components that are not usable by people with disabilities. **Why:** ARIA attributes make web applications more accessible to users with disabilities. **Example:** """jsx const AccessibleButton = ({ children, onClick, ariaLabel }) => ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions <div role="button" aria-label={ariaLabel} tabIndex={0} onClick={onClick} > {children} </div> ); export default AccessibleButton; """ ### Standard 7.2: Semantic HTML **Do This:** Use semantic HTML elements (e.g., "<article>", "<nav>", "<aside>", "<header>", "<footer>") to structure content. Use proper heading hierarchy ("<h1>" through "<h6>"). **Don't Do This:** Use "<div>" and "<span>" elements for all content. **Why:** Semantic HTML improves accessibility and SEO by providing a clear structure to the content. ### Standard 7.3: Keyboard Navigation **Do This:** Ensure that all interactive elements are accessible via keyboard navigation. Use the "tabIndex" attribute to control the focus order. **Don't Do This:** Create components that can only be used with a mouse. ## 8. Performance Optimization (Vercel Specific) ### Standard 8.1: Code Splitting **Do This:** Use dynamic imports to split code into smaller chunks that are loaded on demand. **Don't Do This:** Load all code upfront, especially for large applications. **Why:** Code splitting improves initial load time and reduces the amount of JavaScript that needs to be parsed and executed. **Example:** """jsx import dynamic from 'next/dynamic'; const MyComponent = dynamic(() => import('./MyComponent'), { loading: () => <p>Loading...</p>, // Optional: SSR False for client-side rendering only. ssr: false }); const HomePage = () => ( <div> <h1>Welcome</h1> <MyComponent /> </div> ); export default HomePage; """ ### Standard 8.2: Image Optimization **Do This:** Use the Next.js "<Image>" component or a dedicated image optimization library to optimize images for different devices and screen sizes. Leverage Vercel's automatic image optimization feature. **Don't Do This:** Serve large, unoptimized images that slow down page load times. **Why:** Image optimization reduces the amount of data that needs to be transferred and improves page load times. **Example:** """jsx import Image from 'next/image'; const MyImage = () => ( <Image src="/images/my-image.jpg" alt="My Image" width={500} height={300} priority // Use priority for LCP images /> ); export default MyImage; """ ### Standard 8.3: Memoization Techniques **Do This:** Use "React.memo" to memoize functional components and prevent unnecessary re-renders. Use "useMemo" and "useCallback" hooks to memoize values and functions. **Don't Do This:** Rely solely on manual optimization without using memoization techniques. **Why:** Memoization prevents unnecessary re-renders and improves performance. **Example:** """jsx import React from 'react'; const MyComponent = React.memo(({ data }) => { // This component will only re-render if the 'data' prop changes console.log('Rendering MyComponent'); return <div>{data}</div>; }); export default MyComponent; """ ## 9. Security ### Standard 9.1: Input Validation **Do This**: Validate all user inputs on both the client-side and server-side to prevent attacks such as SQL injection and cross-site scripting (XSS). **Don't Do This**: Trust user input without validation. Rely solely on client-side validation. **Why**: Input validation ensures data is sanitized and conforms to expected formats, preventing malicious data from being processed. **Example**: """javascript // Server-side validation in a Next.js API route export default async function handler(req, res) { if (req.method === 'POST') { const { email, message } = req.body; if (!email || !email.includes('@')) { return res.status(400).json({ error: 'Invalid email address' }); } if (!message || message.length < 10 ) { return res.status(400).json({ error: 'Message must be at least 10 characters long' }); } // Process the data if validation passes try { // Simulate saving to a database console.log('Data received:', { email, message }); return res.status(200).json({ message: 'Message submitted successfully!' }); } catch (error) { console.error('Error processing data:', error); return res.status(500).json({ error: 'Failed to process data' }); } } else { return res.status(405).json({ error: 'Method not allowed' }); } } """ ### Standard 9.2: Output Encoding **Do This:** Encode all data that is displayed on the page to prevent XSS attacks. Use a templating engine or library that automatically encodes output. **Don't Do This:** Output user-supplied data directly without encoding. **Why:** Output encoding ensures that any potentially malicious code is rendered as text, preventing it from being executed in the user's browser. **Example**: Using React (which automatically encodes output): """jsx function DisplayComment({ comment }) { return ( <div> {/* React automatically encodes the comment content, preventing XSS */} <p>{comment}</p> </div> ); } export default DisplayComment; """ ### Standard 9.3: Environment Variables **Do This:** Store sensitive information (e.g., API keys, database passwords) in environment variables and access them securely. Use Vercel's environment variable management feature to store and manage environment variables. **Don't Do This:** Hardcode sensitive information in the codebase. Commit sensitive information to version control. **Why:** Environment variables keep sensitive information separate from the codebase, minimizing the risk of accidental exposure. **Example:** Accessing an environment variable in a Next.js API route: """javascript // pages/api/data.js export default async function handler(req, res) { const apiKey = process.env.API_KEY; if (!apiKey) { console.error('API_KEY not found in environment variables'); return res.status(500).json({ error: 'API_KEY not configured' }); } try { const data = await fetchData(apiKey); res.status(200).json(data); } catch (error) { console.error('Error fetching data:', error); res.status(500).json({ error: 'Failed to fetch data' }); } } """ ## 10. Vercel Specific Considerations ### Standard 10.1: Utilizing Vercel's Build Cache **Do This:** Ensure your project leverages Vercel's build cache effectively. Configure the ".vercelignore" file to exclude unnecessary files from the build cache, improving build times. Use caching headers properly for static assets. **Don't Do This:** Inadvertently invalidate the build cache by including dynamic data or large, frequently changing files in the build. **Why:** Vercel's build cache significantly speeds up deployment times, especially for large projects. ### Standard 10.2: Monitoring and Analytics **Do This:** Utilize Vercel Analytics, or integrate with other analytics platforms to monitor application performance, identify bottlenecks, and track key metrics. Monitor serverless function usage and optimize accordingly. **Don't Do This:** Deploy code without monitoring performance and usage patterns. Ignore performance alerts. **Why:** Monitoring and analytics provide valuable insights into application behavior and help identify areas for optimization. Vercel analytics helps understand user engagement and performance metrics directly within the Vercel platform. ### Standard 10.3: Vercel CLI **Do This:** Utilize the Vercel CLI for local development, preview deployments, and production deployments. **Don't Do This:** Manually deploy code without using the Vercel CLI. **Why:** Vercel CLI simplifies the deployment process and provides a consistent development experience. By adhering to these comprehensive component design standards, development teams can create high-quality, scalable, and maintainable Vercel applications. This document provides a solid foundation for promoting best practices and ensuring consistency throughout the development lifecycle.
# State Management Standards for Vercel This document outlines the coding standards for state management in Vercel applications. It aims to provide clear guidelines for developers to build maintainable, performant, and secure applications that leverage the benefits of the Vercel platform. It is designed to be used by developers and serve as context for AI coding assistants. ## 1. Introduction to State Management on Vercel ### 1.1. State in Serverless Environments Vercel is designed around serverless functions, which are stateless by nature. This means each function invocation is independent and doesn't retain data between calls. Therefore, effective state management is crucial for building dynamic and interactive applications. ### 1.2. Scope of this Document This document focuses on state management strategies relevant in the context of Vercel, covering both client-side and server-side considerations. We'll discuss techniques for managing UI state, data fetching, caching, and persistence, taking advantage of Vercel's features and integrations where possible. The latest version of Vercel is considered when defining standards. ## 2. Client-Side State Management ### 2.1. Choosing a Framework React is the most common framework used with Vercel. While this document isn't React-specific, many examples implicitly use React as the UI layer. Consider these common solutions: * **useState and useContext (React):** Suitable for simple state management within components and sharing state across component trees. * **Redux:** A predictable state container for managing complex application state. * **Zustand:** A small, fast, and scalable bearbones state-management solution using simplified flux principles. * **Recoil:** An experimental state management library from Facebook for React apps. * **Jotai:** Primitive and flexible state management for React with an atomic model. * **TanStack Query (formerly React Query):** For managing, caching, and updating asynchronous data in your React components. * **SWR (Stale-While-Revalidate):** A React Hooks library for remote data fetching. Plays especially well with Vercel's edge network. **Standard:** Choose the state management solution that best fits the complexity and scale of your application. For simple applications, "useState" and "useContext" may be sufficient. For larger applications, consider Redux, Zustand, Recoil, Jotai, TanStack Query, or SWR. **WHY:** Selecting the right tool simplifies development, improves maintainability, and optimizes performance. Avoid over-engineering state management for small applications. ### 2.2. React useState and useContext #### 2.2.1 Standards * **Do This:** Use "useState" for managing local component state. * **Do This:** Use "useContext" for sharing state between components without prop drilling. Combine with a reducer ("useReducer") for complex state updates. * **Don't Do This:** Overuse "useState" for global application state. This makes state difficult to manage and debug. * **Don't Do This:** Mutate state directly. Always use the setter function provided by "useState" or dispatch an action to a reducer. #### 2.2.2 Code Examples """javascript // Example using useState import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } export default Counter; // Example using useContext with useReducer import React, { createContext, useReducer, useContext } from 'react'; const initialState = { count: 0 }; const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: return state; } }; const CounterContext = createContext(); function CounterProvider({ children }) { const [state, dispatch] = useReducer(reducer, initialState); return ( <CounterContext.Provider value={{ state, dispatch }}> {children} </CounterContext.Provider> ); } function useCounter() { return useContext(CounterContext); } function CounterDisplay() { const { state } = useCounter(); return <p>Count: {state.count}</p>; } function CounterButtons() { const { dispatch } = useCounter(); return ( <div> <button onClick={() => dispatch({ type: 'increment' })}>Increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button> </div> ); } function App() { return ( <CounterProvider> <CounterDisplay /> <CounterButtons /> </CounterProvider> ); } export default App; """ **WHY:** Proper usage of "useState" and "useContext" ensures component re-renders only when necessary, optimizing performance. Using "useReducer" with "useContext" centralizes state logic, improving maintainability and predictability. ### 2.3. Redux #### 2.3.1 Standards * **Do This:** Use Redux for managing complex application state that needs to be accessible from multiple components. * **Do This:** Follow the Redux principles of a single source of truth, state is read-only, and changes are made with pure functions. * **Do This:** Use Redux Toolkit to simplify Redux setup and reduce boilerplate code. Redux Toolkit provides "configureStore", "createSlice", and other utilities to make Redux easier to use. This is now the *recommended* approach. * **Don't Do This:** Mutate state directly in reducers. Always return a new state object. Use libraries like Immer to simplify immutable updates. * **Don't Do This:** Store derived data in the Redux store. Instead, calculate derived data using selectors. * **Don't Do This:** Over-rely on Redux for simple applications. Context or Zustand might be more appropriate. #### 2.3.2 Code Example """javascript // Example using Redux Toolkit import { configureStore, createSlice } from '@reduxjs/toolkit'; import { Provider, useDispatch, useSelector } from 'react-redux'; // Define a slice 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 actions export const { increment, decrement, incrementByAmount } = counterSlice.actions; // Configure the store const store = configureStore({ reducer: { counter: counterSlice.reducer, }, }); // React components function Counter() { 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> ); } function App() { return ( <Provider store={store}> <Counter /> </Provider> ); } export default App; """ **WHY:** Redux provides a centralized and predictable state management solution. Redux Toolkit simplifies Redux setup and reduces boilerplate, leading to more maintainable code. ### 2.4. Zustand #### 2.4.1 Standards * **Do This:** Use Zustand for a simple and unopionated state management solution * **Do This:** Define a clear, explicit data flow using setter methods within the Zustand store. * **Don't Do This:** Mutate the state directly. * **Don't Do This:** Create large and complex stores if the app is small. #### 2.4.2 Code Example """javascript import create from 'zustand' const useStore = create(set => ({ bears: 0, increasePopulation: () => set(state => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }) })) function BearCounter() { const bears = useStore(state => state.bears) return <h1>{bears} around here ...</h1> } function BearApp() { return ( <> <BearCounter /> <Buttons /> </> ); } function Buttons() { const increasePopulation = useStore(state => state.increasePopulation) const removeAllBears = useStore(state => state.removeAllBears) return ( <> <button onClick={increasePopulation}>one up</button> <button onClick={removeAllBears}>remove all</button> </> ) } """ **WHY:** Zustand is small with a minimal API, and avoids the boilerplate needed in Redux. ### 2.5. Data Fetching and Caching with SWR or TanStack Query #### 2.5.1 Standards * **Do This:** Use SWR or TanStack Query for fetching, caching, and updating remote data. * **Do This:** Leverage the built-in caching mechanisms of SWR/TanStack Query to reduce network requests and improve performance. * **Do This:** Use optimistic updates to provide a better user experience. * **Do This:** Use "useSWRConfig" in SWR or the QueryClient in TanStack Query to manage cache invalidation and refetching data when necessary. * **Don't Do This:** Manually implement caching logic when using SWR/TanStack Query. * **Don't Do This:** Over-fetch data. Request only the data that is needed. #### 2.5.2 Code Examples """javascript // Example using SWR import useSWR from 'swr'; const fetcher = (...args) => fetch(...args).then(res => res.json()); function Profile() { const { data, error } = useSWR('/api/user', fetcher); if (error) return <div>failed to load</div>; if (!data) return <div>loading...</div>; return ( <div> <h1>{data.name}</h1> <p>{data.bio}</p> </div> ); } export default Profile; // Example using TanStack Query import { useQuery } from '@tanstack/react-query'; const fetcher = async (key) => { const res = await fetch(key); return res.json(); }; function Profile() { const { isLoading, error, data } = useQuery(['user'], () => fetcher('/api/user')); if (isLoading) return <div>Loading...</div>; if (error) return <div>An error has occurred: {error.message}</div>; return ( <div> <h1>{data.name}</h1> <p>{data.bio}</p> </div> ); } export default Profile; """ **WHY:** SWR and TanStack Query simplify data fetching and caching, reduce boilerplate code, and improve performance by leveraging caching and revalidation strategies. SWR also integrates well with Vercel Edge Functions and ISR (Incremental Static Regeneration) for optimal performance. ## 3. Server-Side State Management ### 3.1. Vercel Edge Functions #### 3.1.1 Standards * **Do This:** Use Vercel Edge Functions for handling dynamic requests and implementing server-side logic. * **Do This:** Cache data in Edge Functions to reduce latency and improve performance, especially for frequently accessed data. * **Do This:** Utilize Vercel's Global Edge Network for low-latency data access. * **Don't Do This:** Perform heavy computations or database queries directly in Edge Functions, as they have limited execution time. Offload these tasks to backend services or databases. * **Don't Do This:** Store sensitive data directly in Edge Functions code. Use environment variables or secrets management. #### 3.1.2 Code Example """javascript // Vercel Edge Function example export const config = { runtime: 'edge', }; export default async function handler(req) { // Example of caching data (using a simple in-memory cache) if (handler.cache === undefined) { handler.cache = { data: null, timestamp: 0, }; } const now = Date.now(); const cacheExpiry = 60 * 1000; // 60 seconds if (handler.cache.data === null || now - handler.cache.timestamp > cacheExpiry) { // Fetch data from an external API const res = await fetch('https://api.example.com/data'); const data = await res.json(); // Update the cache handler.cache = { data: data, timestamp: now, }; } return new Response( JSON.stringify(handler.cache.data), { status: 200, headers: { 'content-type': 'application/json', 'cache-control': 'public, max-age=60', // Cache at the edge }, } ); } """ **WHY:** Vercel Edge Functions provide a fast and scalable environment for handling dynamic requests. Caching data at the edge reduces latency and improves performance, leading to a better user experience. ### 3.2. Databases #### 3.2.1 Standards * **Do This:** Choose a database that fits the needs of your application, considering factors like data structure, scalability, and cost. * **Do This:** Use connection pooling to efficiently manage database connections. * **Do This:** Use parameterized queries to prevent SQL injection attacks. * **Don't Do This:** Store sensitive information in plain text. Use encryption to protect sensitive data. * **Don't Do This:** Expose database credentials directly in your code. Use environment variables. #### 3.2.2 Code Example Example with Vercel Postgres and "pg" library: """javascript // Example using Vercel Postgres (Neon) import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.POSTGRES_URL + "?sslmode=require", }); export default async function handler(req, res) { try { const client = await pool.connect(); const result = await client.query('SELECT NOW()'); const now = result.rows[0].now; client.release(); res.status(200).json({ now }); } catch (error) { console.error('Error fetching data:', error); res.status(500).json({ error: 'Failed to fetch data' }); } } export const config = { api: { region: 'iad1', // Or other region }, }; """ **WHY:** Proper database management ensures data integrity, security, and scalability. ### 3.3. Caching Strategies (ISR, SSR, Edge Caching) #### 3.3.1 Standards * **Do This:** Use Incremental Static Regeneration (ISR) for content that is updated frequently but doesn't require real-time updates. * **Do This:** Use Server-Side Rendering (SSR) for content that must be dynamically generated on each request. * **Do This:** Leverage Vercel's Edge Network for caching static assets and API responses. * **Do This:** Configure appropriate "cache-control" headers to specify how long data should be cached. * **Don't Do This:** Over-use SSR, as it can negatively impact performance. Consider ISR as an alternative. * **Don't Do This:** Cache sensitive data or data that should not be shared. #### 3.3.2 Code Example """javascript // Example using ISR in Next.js (Vercel) export async function getStaticProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data, }, revalidate: 60, // Regenerate every 60 seconds }; } function Page({ data }) { return ( <div> <h1>Data from API</h1> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); } export default Page; """ **WHY:** Effective caching strategies reduce server load, improve performance, and provide a better user experience. ISR allows for static generation with dynamic updates, balancing performance and freshness. ## 4. Optimizing for Performance ### 4.1. State Management Libraries When selecting the correct framework and method, make sure you take into account how it will function within Vercel, and it's performance. * **Avoid Memory Leaks:** Ensure components and state updates are handled properly to release memory efficiently. * **Optimize Re-renders:** Choose modern methods such as "useMemo" and "useCallback" to render components only when truly needed. ### 4.2. Data Transfer * **Optimize API Responses:** Compress API responses using gzip or Brotli compression to reduce data transfer sizes. * **Use GraphQL:** Consider using GraphQL to fetch only the data that is needed, reducing over-fetching. ### 4.3. Lazy Loading * **Implement Lazy Loading:** Load components and data only when they are needed to reduce initial load time. * **Code Splitting:** Split your code into smaller chunks to improve initial load time. ## 5. Security Considerations ### 5.1. Protecting Sensitive Data * **Environment Variables:** Store sensitive data such as API keys and database credentials in environment variables. * **Secrets Management:** Use a secrets management tool such as HashiCorp Vault to securely store and manage secrets. * **Encryption:** Encrypt sensitive data at rest and in transit. ### 5.2. Preventing Injection Attacks * **Parameterized Queries:** Use parameterized queries to prevent SQL injection attacks. * **Input Validation:** Validate all user inputs to prevent cross-site scripting (XSS) and other injection attacks. ### 5.3. Authentication and Authorization * **Implement Authentication:** Verify the identity of users before granting access to protected resources. * **Implement Authorization:** Control access to resources based on user roles and permissions. ## 6. Conclusion These coding standards provide a foundation for building robust, performant, secure, and maintainable Vercel applications. By adhering to these guidelines, development teams can ensure consistency, reduce errors, and optimize the overall development process. Remember to adapt and refine these standards based on the specific needs of your project and the evolving landscape of Vercel's capabilities. Regular review and updates to these standards are essential to keep pace with the latest best practices and advancements in the Vercel ecosystem.
# Performance Optimization Standards for Vercel This document outlines coding standards for optimizing the performance of applications deployed on Vercel. It focuses on techniques to improve application speed, responsiveness, and resource usage, specifically within the Vercel ecosystem. These standards facilitate maintainability, security, and deliver an excellent user experience. ## 1. Architectural Considerations ### 1.1. Serverless Functions Optimization **Standard:** Optimize serverless functions for cold starts and execution time. * **Do This:** * Minimize dependencies in serverless functions. * Use async/await for I/O operations. * Cache results where appropriate (e.g., using a distributed cache like Redis or Memcached if higher scale is needed; otherwise, in-memory caching within the function's execution context for subsequent invocations within the same cold start). * Use environment variables to configure the function instead of hardcoding values. * Use optimized data formats like protobuf or FlatBuffers for data serialization/deserialization in performance-critical functions if payload sizes are significant. * **Don't Do This:** * Include unnecessary dependencies. * Perform synchronous I/O operations. * Overuse global variables without proper caching strategies. * Hardcode configuration values. **Why:** Serverless functions are ephemeral; minimizing cold start times and optimizing execution directly improves response times and reduces costs. **Code Example (Node.js):** """javascript // Correct: Optimized serverless function with caching const { createClient } = require('redis'); let redisClient; async function getRedisClient() { if (!redisClient) { redisClient = createClient({ url: process.env.REDIS_URL, }); redisClient.on('error', (err) => console.log('Redis Client Error', err)); // important! await redisClient.connect(); } return redisClient; } export default async function handler(req, res) { const client = await getRedisClient(); const key = 'my-data'; let data = await client.get(key); if (!data) { // Simulate fetching data from a slow data source await new Promise(resolve => setTimeout(resolve, 500)); data = JSON.stringify({ value: 'Expensive operation result' }); await client.set(key, data, { EX: 60 }); // Cache for 60 seconds console.log("Data fetched from source and cached") } else { console.log("Data fetched from cache") } res.status(200).json(JSON.parse(data)); } // Anti-pattern: Synchronous operation, no caching // export default async function handler(req, res) { // // Simulate fetching data from a slow data source without caching // await new Promise(resolve => setTimeout(resolve, 500)); // const data = { value: 'Expensive operation result' }; // res.status(200).json(data); // } """ ### 1.2. Edge Functions Optimization **Standard:** Utilize Edge Functions for tasks requiring low latency and proximity to users. * **Do This:** * Use Edge Functions for A/B testing, authentication, and personalization close to the user. * Keep Edge Functions lightweight and focused. * Leverage the Vercel Edge Network's caching capabilities. * **Don't Do This:** * Perform heavy computations or database queries in Edge Functions. * Use Edge Functions for tasks better suited for serverless functions. **Why:** Edge Functions execute closer to the user, reducing latency. Offloading suitable tasks enhances overall application responsiveness. **Code Example (Edge Function):** """javascript // Correct: Edge Function for geolocation-based redirection export const config = { matcher: '/', }; export function middleware(request) { const country = request.geo.country || 'US'; // Default to US if country cannot be determined. if (country === 'DE') { return Response.redirect(new URL('/de', request.url)); } if (country === 'FR') { return Response.redirect(new URL('/fr', request.url)); } // Default to English for all other countries return Response.redirect(new URL('/en', request.url)); } // Antipattern: Doing complex computations in an Edge Function // export const config = { // matcher: '/', // }; // export function middleware(request) { // let sum = 0; // for (let i = 0; i < 1000000; i++) { // Simulating a CPU-intensive operation // sum += i; // } // console.log("Sum calculated:", sum) // Printing the sum for verification // return Response.next(); // } """ ### 1.3. Image Optimization **Standard:** Implement optimized images for fast loading and reduced bandwidth. * **Do This:** * Use "next/image" component in Next.js for automatic image optimization. "next/image" uses the Vercel Image Optimization API under the hood. * Serve images in WebP or AVIF format. * Use responsive images with different sizes for various screen sizes. * Lazy load images below the fold. * Optimize images before uploading (e.g., using tools like ImageOptim or TinyPNG). * **Don't Do This:** * Serve large, unoptimized images. * Use fixed-size images without considering different screen sizes. * Load all images on initial page load. **Why:** Optimized images significantly improve page load times, enhancing user experience, and reducing bandwidth costs. "next/image" and Vercel's Image Optimization API provides these optimizations out-of-the-box. **Code Example (Next.js):** """jsx // Correct: Using next/image for optimized images import Image from 'next/image'; function MyComponent() { return ( <Image src="/images/my-image.jpg" alt="My Image" width={500} // Required height={300} // Required layout="responsive" // Important for responsiveness priority // Loads the image eagerly, good for LCP images /> ); } export default MyComponent; // Anti-pattern: Using a standard <img> tag without optimization // function MyComponent() { // return ( // <img src="/images/my-image.jpg" alt="My Image" width="500" height="300" /> // ); // } """ ## 2. Code-Level Optimizations ### 2.1. Efficient Data Fetching **Standard:** Minimize data fetching and optimize queries to reduce latency and resource usage. * **Do This:** * Use GraphQL with tools like Apollo Client or Relay to fetch only the necessary data. * Implement data caching strategies (e.g., using "stale-while-revalidate" or ISR - Incremental Static Regeneration). * Use efficient database queries with proper indexing. * Utilize Server Components for data fetching at build time. * **Don't Do This:** * Over-fetch data. * Make unnecessary API calls. * Execute inefficient database queries. * Fetch data on the client-side when it can be done server-side or at build time. **Why:** Efficient data fetching reduces network overhead and server load, resulting in faster response times and improved scalability. **Code Example (Next.js with ISR):** """javascript // Correct: Using Incremental Static Regeneration (ISR) for data fetching export async function getStaticProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data, }, revalidate: 60, // Regenerate every 60 seconds }; } function MyComponent({ data }) { return ( <div> {data.map((item) => ( <div key={item.id}>{item.name}</div> ))} </div> ); } export default MyComponent; // Anti-pattern: Fetching data on the client-side without caching or ISR // import { useState, useEffect } from 'react'; // function MyComponent() { // const [data, setData] = useState([]); // useEffect(() => { // async function fetchData() { // const res = await fetch('https://api.example.com/data'); // const data = await res.json(); // setData(data); // } // fetchData(); // }, []); // return ( // <div> // {data.map((item) => ( // <div key={item.id}>{item.name}</div> // ))} // </div> // ); // } // export default MyComponent; """ ### 2.2. Code Splitting and Lazy Loading **Standard:** Implement code splitting and lazy loading to reduce initial JavaScript bundle size. * **Do This:** * Use dynamic imports ("import()") for components and modules. * Leverage Next.js's automatic code splitting. * Lazy load components that are not immediately visible (e.g., using "React.lazy" and "Suspense"). * **Don't Do This:** * Load all JavaScript on initial page load. * Include unused code in the initial bundle. **Why:** Code splitting reduces the initial JavaScript bundle size, resulting in faster initial page load times. **Code Example (React.lazy):** """jsx // Correct: Lazy loading a component import React, { lazy, Suspense } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); function App() { return ( <div> <h1>My App</h1> <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> </div> ); } export default App; // Anti-pattern: Importing all components eagerly // import MyComponent from './MyComponent'; // function App() { // return ( // <div> // <h1>My App</h1> // <MyComponent /> // </div> // ); // } // export default App; """ ### 2.3. Memoization and Component Optimization **Standard:** Optimize React components using memoization techniques. * **Do This:** * Use "React.memo" to prevent unnecessary re-renders of components. * Use "useMemo" and "useCallback" hooks to memoize values and functions. * Avoid passing new objects or functions as props to components. * Use "PureComponent" for class components when applicable. * **Don't Do This:** * Rely on re-renders when the component's props or state haven't changed. * Create new objects or functions inline in the render method. **Why:** Memoization prevents unnecessary re-renders, resulting in improved performance, especially for complex components. **Code Example (React.memo):** """jsx // Correct: Using React.memo to prevent unnecessary re-renders import React from 'react'; const MyComponent = React.memo(function MyComponent({ data }) { console.log('MyComponent rendered'); return <div>{data.value}</div>; }); function App() { const [count, setCount] = React.useState(0); const data = React.useMemo(() => ({ value: count }), [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> <MyComponent data={data} /> </div> ); } export default App; // Anti-pattern: Re-rendering a component unnecessarily // function MyComponent({ data }) { // console.log('MyComponent rendered'); // return <div>{data.value}</div>; // } // function App() { // const [count, setCount] = React.useState(0); // const data = { value: count }; // New object on every render! // return ( // <div> // <button onClick={() => setCount(count + 1)}>Increment</button> // <MyComponent data={data} /> // </div> // ); // } // export default App; """ ## 3. Caching Strategies ### 3.1. HTTP Caching **Standard:** Leverage HTTP caching to reduce server load and improve response times. * **Do This:** * Set appropriate "Cache-Control" headers on static assets (e.g., "max-age", "immutable"). * Use CDNs (Content Delivery Networks) to cache and serve static assets closer to users. * Implement "ETag" headers for conditional requests. * **Don't Do This:** * Disable caching for static assets. * Use overly aggressive or overly lenient caching policies. **Why:** HTTP caching offloads static asset delivery to the browser and CDNs, reducing server load and improving response times significantly. Vercel automatically handles much of this, but understanding caching headers allows for fine-tuning. **Code Example (Next.js with custom headers):** """javascript // next.config.js module.exports = { async headers() { return [ { source: '/_next/static/(.*)', headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', // Cache for 1 year }, ], }, ]; }, }; """ ### 3.2. Data Caching **Standard:** Implement data caching to reduce database or API load and improve response times. * **Do This:** * Use in-memory caching for frequently accessed data. Be mindful of serverless function lifecycles. * Utilize a distributed cache (e.g., Redis, Memcached) for shared data across multiple serverless functions. * Implement cache invalidation strategies to ensure data consistency. * Leverage Vercel's Data Cache for Next.js applications * **Don't Do This:** * Cache sensitive data without proper security measures. * Cache data indefinitely without invalidation. * Over-cache data, leading to stale results. **Why:** Data caching reduces latency and resource consumption by serving data from a cache instead of repeatedly fetching it from the original source. **Code Example (Vercel Data Cache):** """javascript // pages/api/data.js import { unstable_cache } from 'next/cache'; async function getData() { return unstable_cache( async () => { // Simulate fetching data from a database console.log('Fetching data from database'); await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate a 1 second delay return { message: 'Hello from the database!' }; }, ['my-data'], // cache key. Invalidating this key will force a refresh { revalidate: 60, // Cache for 60 seconds tags: ['data'] // Tags for purging the cache. Useful for grouped purges } )(); } export default async function handler(req, res) { const data = await getData(); res.status(200).json(data); } """ """javascript // app/page.js import { revalidateTag, unstable_cache } from 'next/cache'; async function getData() { return unstable_cache( async () => { // Simulate fetching data from a database console.log('Fetching data from database'); await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate a 1 second delay return { message: 'Hello from the database component!' }; }, ['my-data-component'], // cache key. Invalidating this key will force a refresh { revalidate: 60, // Cache for 60 seconds tags: ['data-component'] // Tags for purging the cache } )(); } export default async function Page() { const data = await getData(); return ( <div> <p>Data from server: {data.message}</p> </div> ); } """ ### 3.3. Static Site Generation (SSG) **Standard:** Utilize Static Site Generation (SSG) for content that rarely changes. * **Do This:** * Use SSG for blogs, documentation, and other static content. * Regenerate static pages on content updates. * **Don't Do This:** * Use SSG for dynamic content that requires frequent updates. **Why:** SSG generates static HTML files at build time, resulting in extremely fast page load times and reduced server load. Vercel excels at serving statically generated content. **Code Example (Next.js /app directory SSG - layout.tsx or page.tsx):** """typescript // app/page.tsx export const metadata = { title: 'About Us', description: 'Information about our company', }; export default function AboutPage() { return ( <div> <h1>About Us</h1> <p>This is a statically generated page.</p> </div> ); } """ ## 4. Monitoring and Optimization Tools ### 4.1. Vercel Analytics **Standard:** Use Vercel Analytics to identify performance bottlenecks and optimize application performance. * **Do This:** * Monitor key metrics such as page load times, Core Web Vitals, and function execution times. * Use insights from Vercel Analytics to identify areas for optimization. * Set up alerts for performance regressions. * **Don't Do This:** * Ignore performance data. * Fail to address identified performance issues. **Why:** Vercel Analytics provides valuable insights into application performance, enabling data-driven optimization. ### 4.2. Lighthouse and WebPageTest **Standard:** Regularly audit your application using Lighthouse and WebPageTest. * **Do This:** * Run Lighthouse audits to identify opportunities for improvement in performance, accessibility, and SEO. * Use WebPageTest to analyze website performance from different locations and network conditions. * Implement recommendations from Lighthouse and WebPageTest. * **Don't Do This:** * Ignore recommendations from performance audits. * Fail to test application performance in various environments. **Why:** Lighthouse and WebPageTest provide detailed performance reports, helping identify and address performance bottlenecks. ## 5. Vercel Specific Optimizations ### 5.1. Vercel KV When using Vercel KV (Redis), be cognizant of latency inherent in accessing a remote datastore, even one co-located in the same region as your functions. * **Do This:** * Cache frequently accessed data retrieved from Vercel KV * Batch operations when feasible. Multiple "get" or "set" operations can be combined into a single command. * Consider using a Redis client library optimized for serverless environments that handles connection pooling and reconnection efficiently. **Why:** Minimizing round trips to Vercel KV and optimizing the efficiency of operations reduces latency and improves function execution time. **Code Example:** """javascript import { kv } from "@vercel/kv"; export async function POST(req: Request): Promise<Response> { const body = await req.json() const {message} = body if (!message){ return new Response("Missing message", {status: 400}) } try { // Batch operation: set message and increment counter in a single pipeline await kv.pipeline() .set('latestMessage', message) .incr('messageCount') .exec(); return new Response("OK", {status: 200}) } catch (error: any) { console.error('Error setting message:', error); return new Response(error.message, { status: 500 }); } } """ ### 5.2. Utilizing Vercel's Global CDN **Standard:** Ensure proper configuration to maximize the benefits of Vercel's Global CDN. * **Do This:** * Leverage automatic static asset caching provided by Vercel. * Configure appropriate cache control headers for dynamic content. * Consider using Vercel's Pro plan features like Global CDN for improved performance across different geographic locations. * **Don't Do This:** * Bypass or misconfigure the CDN by setting excessively short cache durations. * Serve sensitive data through the CDN without implementing proper security measures. **Why:** Vercel's Global CDN automatically distributes your application's static assets across a network of edge locations, reducing latency and improving performance for users worldwide. ### 5.3. Environment Variable Optimization for Vercel **Standard:** Properly manage and optimize environment variables for Vercel deployments. * **Do This:** * Store configuration values and secrets as environment variables. * Use Vercel's environment variable management features to securely store and manage environment variables across different environments (development, preview, production). * Minimize the number of environment variables accessed during runtime to reduce lookup overhead. * **Don't Do This:** * Hard-code sensitive information in your code or configuration files. * Store environment variables directly in your repository. * Expose sensitive environment variables to the client-side. **Why:** Securely managing environment variables is essential for application security and configuration management. Additionally, optimizing environment variable access improves application performance.