# Deployment and DevOps Standards for Vercel
This document outlines the deployment and DevOps standards for projects hosted on Vercel. Adhering to these standards ensures maintainability, performance, security, and a smooth development lifecycle. These standards are aligned with the most recent Vercel features and best practices.
## 1. Build Processes and CI/CD
### 1.1. Standard: Automated Builds and Deployments
**Description:** Every code change should trigger an automated build, test, and deployment pipeline. This ensures consistent deployments and reduces human error.
**Why:** Automated pipelines improve deployment speed, repeatability, and reliability. They also enable faster feedback loops and easier rollbacks.
**Do This:**
* Use Vercel's built-in Git integration for automatic deployments on every push to a branch configure in your Vercel project settings.
* Utilize Vercel's Preview Deployments for every pull request.
* Configure environment variables appropriately for different Vercel environments (Production, Preview, Development).
**Don't Do This:**
* Manually deploy code changes through the Vercel CLI without proper Git integration.
* Store sensitive information (API keys, database passwords) directly in code or the repository.
* Bypass the automated build process for what you consider "small changes".
**Code Example (vercel.json configuration):**
"""json
// vercel.json
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"routes": [
{
"src": "/api/(.*)",
"dest": "/api/$1"
},
{
"src": "/(.*)",
"dest": "/$1"
}
],
"env": {
"DATABASE_URL": "@database-url",
"NODE_ENV": "production"
},
"probes": [
{
"path": "/api/health",
"readiness": true,
"initialDelay": 3000,
"interval": 5000
}
]
}
"""
**Explanation:**
* "version": Specifies the Vercel configuration version.
* "builds": Defines how to build the project. In this case it uses "@vercel/next" to deploy next.js.
* "routes": Defines how incoming requests are routed to different parts of your application.
* "env": Defines environment variables which are crucial for differing configurations across environments. Using "@database-url" allows referencing secrets stored in Vercel rather than directly storing values.
* "probes": Defines health check endpoints for the Vercel platform to determine the operational status of your application.
**Anti-Pattern:** Committing API keys directly in the repository.
"""javascript
// DON'T DO THIS:
const API_KEY = "YOUR_SECRET_API_KEY";
"""
Instead, utilize Vercel's environment variables or secrets store:
"""javascript
// DO THIS:
const API_KEY = process.env.API_KEY; // Configure API_KEY in Vercel's settings
"""
### 1.2. Standard: Preview Environments
**Description:** Each pull request should automatically create a unique preview environment on Vercel.
**Why:** Allows stakeholders to review and test changes in a production-like environment before merging them into the main branch. This reduces the risk of introducing bugs into production.
**Do This:**
* Utilize Vercel's automatic preview deployment feature on all pull requests.
* Include meaningful names in the pull requests. Vercel will use these names in naming preview environments automatically, increasing traceability.
* Test environment-specific features using environment variables.
**Don't Do This:**
* Disable Vercel's preview deployment feature.
* Assume that code works on your local machine without deploying to a preview environment first.
* Merge untested or unreviewed code into the main branch.
**Code Example (Environment Variable for Preview Environments):**
"""javascript
// utils/config.js
const isProduction = process.env.NODE_ENV === 'production';
const isPreview = process.env.VERCEL_ENV === 'preview';
const config = {
apiUrl: isProduction ? 'https://your-production-api.com' : (isPreview ? 'https://your-preview-api.com' : 'http://localhost:3001'),
featureFlagEnabled: isProduction || isPreview // Enable production/preview features only
};
export default config;
"""
**Explanation:**
* Checks the "NODE_ENV" for production environments.
* Checks the "VERCEL_ENV" to identify preview environment. "VERCEL_ENV" is automatically set by Vercel.
* Dynamically sets "apiUrl" based on the environment, allowing the app to connect to the correct backend in each context.
**Anti-Pattern:** Hardcoding environment-specific configurations directly into components:
"""javascript
// DON'T DO THIS:
function MyComponent() {
const apiUrl = window.location.hostname === 'your-production-url.com' ? 'https://your-production-api.com' : 'http://localhost:3001';
// ...
}
"""
### 1.3. Standard: Staging Environments
**Description:** Utilize Vercel's branch aliasing feature to create a staging environment. This environment mirrors production closely for final testing.
**Why:** Provides a dedicated environment for rigorous testing and stakeholder approval before promoting changes to production.
**Do This:**
* Alias a specific branch such as 'staging' (or a mirror of 'main' or 'production') the "staging" environment in Vercel.
* Run end-to-end tests and user acceptance tests in the staging environment.
* Use the staging environment to validate database migrations and other infrastructure changes.
**Don't Do This:**
* Treat the staging environment as a playground for development.
* Skip staging and deploy directly production after development.
* Allow untested code from feature branches to exist in staging.
**Code Example (vercel.json to specifically manage staging):**
"""json
{
"version": 2,
"alias": [
{
"source": "staging.your-project.vercel.app",
"destination": "your-project.vercel.app/staging"
}
]
}
"""
**Explanation:**
This alias configuration maps the "staging" subdomain to a specific internal route on Vercel. When deploying to the "staging" environment (configured in Vercel project settings), Vercel will ensure that builds utilize this configuration. You'd push code to the "staging" branch (or whatever your staging branch name is) in Git and Vercel will handle the deployment to "staging.your-project.vercel.app".
**Anti-Pattern:** Using the production database or live user data within the staging environment. Always use a separate database or anonymized data for staging.
## 2. Production Considerations
### 2.1. Standard: Monitoring and Error Tracking
**Description:** Implement comprehensive monitoring and error tracking to identify and resolve issues in production.
**Why:** Real-time visibility into application health is crucial for maintaining uptime and performance. Proactive monitoring enables quick responses to unexpected errors.
**Do This:**
* Use Vercel's built-in analytics or integrate with third-party monitoring tools. Sentry is a popular choice.
* Implement proper logging at different levels (info, warning, error) using structured logging.
* Set up alerts for critical errors and performance bottlenecks.
**Don't Do This:**
* Rely solely on user reports to identify issues.
* Ignore error logs or alerts.
* Log sensitive data.
**Code Example (Frontend Error Tracking with Sentry and Next.js):**
"""javascript
// pages/_app.js
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: process.env.NODE_ENV
});
function MyApp({ Component, pageProps }) {
return ;
}
export default MyApp;
"""
"""javascript
// pages/api/error.js
import * as Sentry from "@sentry/nextjs";
function handler(req, res) {
try {
throw new Error("Testing Sentry Error Tracking!");
} catch (error) {
Sentry.captureException(error);
res.status(500).json({ message: "Error reported to Sentry!" });
}
}
export default Sentry.withSentry(handler);
"""
**Explanation:**
* The example initializes Sentry in "_app.js" to capture client-side errors. Browser tracing is enabled for performance monitoring. Replays capture user sessions when errors occur, which aids in debugging front-end issues.
* The API route example demonstrates how to manually capture exceptions within the backend code. "Sentry.captureException(error)" logs the error in Sentry. Further, Sentry's API route enhancement is used with "Sentry.withSentry(handler)".
**Anti-Pattern:** Logging excessively without structured formatting, making it challenging to analyze logs.
### 2.2. Standard: Rollback Strategy
**Description:** Have a well-defined rollback strategy in place in case of a failed deployment.
**Why:** Minimizes downtime and reduces impact on users when a deployment introduces critical errors.
**Do This:**
* Utilize Vercel's automatic rollback feature (if available and if the platform you use supports rollback) to revert to the previous deployment.
* Maintain a backup of your database and application configuration.
* Test the rollback strategy in the staging environment.
**Don't Do This:**
* Attempt to fix issues directly in production without a rollback plan.
* Make significant database changes without a proper backup.
**Code Example (Manual Rollback via Vercel CLI - illustrative, automatic rollbacks are preferred):**
"""bash
# Assuming you want to rollback to a specific deployment ID:
vercel alias production .vercel.app your-project.vercel.app
"""
**Explanation:**
This command (for illustration only, check Vercel's latest documentation for correct CLI usage) would alias the specified deployment (identified by "") back to the production domain.
**Anti-Pattern:** Not having any rollback plan, relying on fixing problems in production without a safety net.
### 2.3. Standard: Performance Optimization
**Description:** Continuously monitor and optimize application performance to ensure a fast and responsive user experience.
**Why:** Performance is crucial for user satisfaction, SEO ranking, and conversion rates. Optimized applications reduce infrastructure costs.
**Do This:**
* Use Vercel's built-in performance analytics to identify bottlenecks.
* Optimize images using tools like "next/image" or Vercel's Image Optimization API.
* Implement code splitting to reduce initial load time.
* Cache frequently accessed data using Vercel's Edge Cache or a dedicated caching layer.
* Monitor and optimize database queries.
* Use CDN features to distribute static assets geographically for faster delivery.
**Don't Do This:**
* Ignore performance metrics.
* Load large, unoptimized images.
* Serve unnecessary JavaScript or CSS.
**Code Example (Image Optimization with "next/image"):**
"""jsx
import Image from 'next/image'
function MyComponent() {
return (
)
}
export default MyComponent;
"""
**Explanation:**
* "next/image" component automatically optimizes images for different devices and viewports. It lazy loads images to improve initial page load.
**Anti-Pattern:** Serving large, uncompressed images directly:
"""html
// DON'T DO THIS:
"""
## 3. Security Best Practices
### 3.1. Standard: Secure Environment Variables
**Description:** Store all sensitive information (API keys, database passwords, etc.) as environment variables.
**Why:** Prevents accidental exposure of secrets in code or version control.
**Do This:**
* Use Vercel's environment variables feature to store secrets.
* Avoid hardcoding secrets directly in the codebase.
* Rotate keys regularly.
**Don't Do This:**
* Commit secrets to version control.
* Expose secrets in client-side code.
**Code Example (Accessing Environment Variables):**
"""javascript
const API_KEY = process.env.API_KEY;
// Use the API_KEY in your application logic
"""
**Explanation:**
* "process.env.API_KEY" retrieves the value of the "API_KEY" environment variable. Configure this variable in the Vercel project settings.
**Anti-Pattern:** Hardcoding API keys directly into the code.
### 3.2. Standard: Dependency Management
**Description:** Keep dependencies up-to-date to patch security vulnerabilities.
**Why:** Outdated dependencies often contain known vulnerabilities that can be exploited by attackers.
**Do This:**
* Regularly update dependencies using "npm update", "yarn upgrade", or similar tools.
* Use dependency scanning tools like "npm audit" or "yarn audit" to identify vulnerabilities.
* Automate dependency updates using tools like Dependabot.
**Don't Do This:**
* Ignore security warnings from dependency scanning tools.
* Use outdated dependencies without assessing and mitigating the risks.
**Code Example (Using "npm audit"):**
"""bash
npm audit
"""
**Explanation:**
* "npm audit" scans the project's dependencies for known vulnerabilities and provides recommendations on how to fix them.
### 3.3. Standard: Input Validation and Sanitization
**Description:** Validate and sanitize all user inputs to prevent injection attacks.
**Why:** Prevents malicious users from injecting code or manipulating data through application inputs.
**Do This:**
* Use appropriate validation libraries to validate user input.
* Escape or sanitize user input before displaying it in the UI or storing it in the database.
* Implement rate limiting to prevent brute-force attacks.
**Don't Do This:**
* Trust user input without validation.
* Expose raw user input in SQL queries or HTML.
**Code Example (Input Validation in a Next.js API Route):**
"""javascript
import { check, validationResult } from 'express-validator';
export default async function handler(req, res) {
await check('email').isEmail().run(req);
await check('password').isLength({ min: 5 }).run(req);
const result = validationResult(req);
if (!result.isEmpty()) {
return res.status(400).json({ errors: result.array() });
}
// Process the validated input
const { email, password } = req.body;
// ...
}
"""
**Explanation:**
* "express-validator" is used to validate the "email" and "password" fields in the request body.
* If validation fails, an error response is returned with the validation errors.
## 4. Modern Deployment Patterns
### 4.1. Standard: GitOps
**Description:** Adopt a GitOps approach where deployments are triggered by changes in the Git repository.
**Why:** Improves auditability, consistency, and repeatability of deployments.
**Do This:**
* Treat your Vercel configuration (e.g., "vercel.json") as code and store it in the Git repository.
* Use pull requests to propose and review changes to the deployment configuration.
* Automate deployments based on Git events (e.g., pushes, merges).
**Don't Do This:**
* Make manual changes to the Vercel configuration outside of the Git repository.
* Bypass the Git-based deployment process.
**Code Example (Illustrative - GitOps Workflow):**
1. Developer creates a new feature branch and makes changes.
2. Developer submits a pull request with changes to the "vercel.json" file or application code.
3. Vercel automatically creates a preview deployment for the pull request.
4. Reviewers approve the pull request.
5. Merging the pull request to the main/production branch triggers a production deployment.
### 4.2. Standard: Infrastructure as Code (IaC)
**Description:** Manage your Vercel infrastructure and configuration using code.
**Why:** Improves consistency, repeatability, and version control of infrastructure.
**Do This:**
* Not directly applicable to Vercel in the traditional cloud provider sense (AWS CloudFormation, Terraform), but the "vercel.json" file represents the "infrastructure" definition for your deployments on Vercel. Treat this with IaC principles.
* Store the Vercel configuration (e.g., "vercel.json") in the Git repository.
* Automate infrastructure provisioning and updates using CI/CD pipelines.
* Define Vercel environment variables and secrets as code
**Don't Do This:**
* Manually configure Vercel projects through the UI.
* Fail to version control.
### 4.3. Standard: Edge Computing and Vercel Edge Functions
**Description:** Leverage Vercel Edge Functions to execute code at the edge of the network, closer to users.
**Why:** Reduces latency, improves performance, and enhances user experience.
**Do This:**
* Use Edge Functions for tasks such as authentication, authorization, A/B testing, and personalization.
* Optimize Edge Functions for performance and security.
* Monitor Edge Function execution time and error rates.
**Don't Do This:**
* Use Edge Functions for long-running or computationally intensive tasks. offload those to serverless functions.
* Store sensitive data directly in Edge Functions.
**Code Example (Basic Vercel Edge Function with Next.js):**
"""javascript
// pages/api/edge.js
export const config = {
runtime: 'edge',
};
export default async function handler(req) {
const data = {
message: 'Hello from the Edge!',
time: new Date().toISOString(),
};
return new Response(JSON.stringify(data), {
status: 200,
headers: {
'content-type': 'application/json',
},
});
}
"""
**Explanation:**
* "runtime: 'edge'" configures the function to run at the edge using Vercel's Edge Runtime.
* The function returns a JSON response with a message and the current time.
## 5. Vercel-Specific Considerations
### 5.1. Standard: Utilize Vercel Remote Cache
**Description:** Leverage the Vercel Remote Cache with Turborepo to drastically cut down on build times.
**Why:** Build caching significantly accelerates the deployment process by reusing already-built artifacts.
**Do This:**
* Enable the Vercel Remote Cache inside of your Vercel project settings when using Turborepo.
* Ensure Turborepo is configured properly in your "turbo.json" to take advantage of caching.
**Don't Do This:**
* Disable remote caching when viable, as it adds significant time savings.
* Store senstive info or rely on the Vercel Remote Cache as your ONLY source of truth. It's designed as a temporary performance enhancer, not persistant storage.
"""json
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"test": {},
"lint": {}
},
"globalDependencies": ["tsconfig.json"],
"globalEnv": ["NODE_ENV", "VERCEL"]
}
"""
**Explanation:** In "turbo.json" we specify our turborepo configuration. "outputs" dictate which files are cached after a build is complete.
### 5.2. Standard: Prioritize Vercel Functions Configuration Options
**Description:** Properly configure Vercel Functions to manage runtime effectively based on workload profile.
**Why:** Improper configuration can lead to unexpected costs or performance constraints.
**Do This:**
* Understand and configure "memory", "maxDuration", Regions, and "concurrency" based on the funciton workload.
**Don't Do This:**
* Use default settings for all functions. Review each function's needs.
* Over-allocate resources without justification.
"""json
// vercel.json
{
"functions": {
"api/heavy-computation.js": {
"memory": 2048,
"maxDuration": 60,
"regions": ["iad1"]
},
"api/image-resize.js": {
"memory": 512,
"maxDuration": 10,
"regions": ["sfo1", "iad1", "fra1"]
}
}
}
"""
**Explanation:**
* The configuration shows how to configure maximum memory, maximum duration, and regions on a per-function basis.
* Tailor these settings to each functions needs.
By consistently adhering to these Deployment and DevOps standards, development teams can build, deploy, and maintain high-quality applications on Vercel with improved efficiency, reliability, and security.
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.