# Security Best Practices Standards for Astro
This document outlines security best practices for Astro projects, focusing on preventing common vulnerabilities, implementing secure coding patterns, and leveraging Astro's features to enhance application security.
## 1. General Security Principles
These foundational principles should guide all Astro development decisions.
* **Principle of Least Privilege:** Grant only the minimum necessary permissions to users and components.
* **Do This:** Design your application so each component (e.g., server endpoints, API routes) only has the permissions needed to perform its specific task.
* **Don't Do This:** Grant blanket permissions or run components with administrative privileges when they are not required.
* **Why:** Limits the potential damage if a component is compromised.
* **Defense in Depth:** Implement multiple layers of security controls. If one fails, others are in place.
* **Do This:** Combine input validation, output encoding, rate limiting, and regular security audits.
* **Don't Do This:** Rely on a single security measure as the sole protection mechanism.
* **Why:** Prevents a single vulnerability from leading to a complete system compromise.
* **Keep Security Simple:** Favor simple, well-understood security mechanisms over complex, custom solutions.
* **Do This:** Use established libraries and frameworks for authentication, authorization, and encryption.
* **Don't Do This:** Roll your own cryptographic algorithms or authentication schemes.
* **Why:** Reduces the risk of introducing subtle flaws, increasing the chances of being exploited, and reduces the time spent debugging.
* **Regular Audits and Updates:** Conduct regular security audits and keep dependencies up-to-date.
* **Do This:** Use security linters, dependency scanners, and penetration tests. Monitor components for known vulnerabilities.
* **Don't Do This:** Neglect security audits or postpone applying security patches.
* **Why:** Ensures you quickly learn about and address new vulnerabilities.
## 2. Preventing Cross-Site Scripting (XSS)
XSS vulnerabilities allow attackers to inject malicious scripts into your web pages. Astro, fortunately mitigates this by default by treating all files as components (rather than raw HTML).
* **Input Validation and Output Encoding:** Always validate user input and encode output displayed on the page.
* **Do This:** Use Astro's component-based architecture to escape HTML entities server-side or at build time. Be aware of what could be dangerous and escape those specific characters.
* **Don't Do This:** Directly insert user-provided data into the HTML without proper escaping.
### Example: Encoding User Input
"""astro
---
const userInput = Astro.request.url.searchParams.get('query') || ''; // Get user defined search query
const encodedInput = encodeURIComponent(userInput); //Encoding user input
---
<p>You searched for: {encodedInput}</p>
"""
*Why: Encoding converts special characters to their HTML entities, preventing them from being interpreted as code.*
### Anti-Pattern: Directly Inserting Input
"""astro
<p>You searched for: {Astro.request.url.searchParams.get('query') || ''}</p>
"""
*Why: This allows execution of arbitrary javascript if a malicious user were to exploit it.*
* **Use "sanitize-html" for Rich Text Content:** If you need to allow some HTML tags (e.g., for rich text content), use a library like "sanitize-html".
### Example: Using "sanitize-html"
First, install the package:
"""bash
npm install sanitize-html
"""
Then, use it in your Astro component:
"""astro
---
import sanitizeHtml from 'sanitize-html';
const unsafeHtml = '<p>Hello</p>';
const safeHtml = sanitizeHtml(unsafeHtml, {
allowedTags: ['p'],
allowedAttributes: {},
});
---
"""
*Why: This library allows you to define which HTML tags and attributes are safe to include, removing any potentially malicious code.*
## 3. Preventing Cross-Site Request Forgery (CSRF)
CSRF attacks trick users into performing actions they didn't intend to, often by submitting unauthorized requests.
* **Implement CSRF Tokens:** Include a unique, unpredictable token in your forms and verify it on the server.
* **Do This:** Generate a CSRF token and store it in the user's session or a cookie. Include the token as a hidden field in your forms and validate it when the form is submitted. Frameworks usually provide built-in CSRF protection mechanisms.
* **Don't Do This:** Rely on simple techniques like checking the "Referer" header, as these can be easily bypassed.
### Example: CSRF Protection with a Custom Implementation in an API endpoint
This showcases a custom implementation. Real applications should use a library.
"""astro
// api/submit-form.js
import { randomBytes } from 'crypto';
// Function to generate a CSRF token
function generateCsrfToken() {
return randomBytes(64).toString('hex');
}
// Function to verify the CSRF token
function verifyCsrfToken(sessionToken, submittedToken) {
return sessionToken === submittedToken;
}
//Generate the CSRF Token
const csrfToken = generateCsrfToken();
// Set the CSRF token in a secure, HTTP-only cookie
Astro.response.headers.set('Set-Cookie', "csrfToken=${csrfToken}; Secure; HttpOnly; Path=/");
//Handle form request with protected CSRF Token verification
export async function post({request}){
const formData = await request.formData();
const submittedToken = formData.get('csrfToken');
const sessionToken = Astro.cookies.get('csrfToken').value;
if(!verifyCsrfToken(sessionToken, submittedToken)) {
return new Response(JSON.stringify({ message: 'CSRF token validation failed' }), {
status: 400,
headers: {
"Content-Type": "application/json"
}
});
}
//Process the legit request here.
}
"""
*Why: CSRF tokens ensure that the user intentionally submitted the request and that the request did not originate from a malicious site.*
* **Use SameSite Cookies:** Set the "SameSite" attribute on your cookies to "Strict" or "Lax" to prevent cross-site requests from accessing your cookies. Setting to "Strict" will prevent requests from other origins entirely. "Lax" provides defense with reasonable usability (e.g. GET requests still work on cross-origin links).
### Example: Setting "SameSite" Attribute
"""javascript
//Setting SameSite cookie
Astro.response.headers.set('Set-Cookie', "sessionid=12345; Secure; HttpOnly; Path=/; SameSite=Strict");
"""
*Why: This tells the browser whether to send the cookie along with cross-site requests, mitigating the impact of CSRF attacks.*
## 4. Secure Authentication and Authorization
Properly securing user authentication and authorization is critical.
* **Use Strong Password Hashing:** Never store passwords in plain text. Use a strong password hashing algorithm like bcrypt or Argon2.
* **Do This:** Use a library suited for password and authentication management such as Auth.js.
* **Don't Do This:** Use weak hashing algorithms like MD5 or SHA1.
### Example: Password Hashing with "bcrypt"
First, install "bcrypt":
"""bash
npm install bcrypt
"""
Then, use it in your authentication logic:
"""javascript
import bcrypt from 'bcrypt';
async function hashPassword(password) {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
return hashedPassword;
}
async function verifyPassword(password, hashedPassword) {
const match = await bcrypt.compare(password, hashedPassword);
return match;
}
"""
*Why: Strong hashing algorithms make it computationally infeasible for attackers to recover passwords from stolen password databases.*
* **Implement Multi-Factor Authentication (MFA):** Require users to provide multiple forms of authentication, such as a password and a one-time code from a mobile app.
* **Do This:** Integrate a library or service that provides MFA functionality.
* **Don't Do This:** Rely solely on passwords for authentication.
* **Implement Role-Based Access Control (RBAC):** Define roles with specific permissions and assign users to these roles.
* **Do This:** Use a library that simplifies RBAC, tying it to specific Astro pages or API endpoints based on the logged-in user's permissions.
* **Don't Do This:** Grant all users the same level of access or hardcode authorization checks.
### Example: Basic Example of RBAC for an API Endpoint
"""javascript
// auth.js - a hypothetical authentication module
import { getUserRole } from './auth'; // Function that returns the user's role from their authentication record
export async function get({request}){
const role = getUserRole(request);
if (role !== 'admin') {
return new Response(JSON.stringify({
message: 'Unauthorized',
}), {
status: 403,
headers: {
'Content-Type': 'application/json',
},
});
}
// Admin-only logic here
const data = { message: 'Admin access granted!' };
return new Response(JSON.stringify(data), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
};
"""
*Why: RBAC allows you to control who can access which resources and perform which actions, minimizing the impact of a compromised account.*
## 5. Secure Data Handling
Protect sensitive data both in transit and at rest.
* **Use HTTPS:** Always use HTTPS to encrypt communication between the client and the server.
* **Do This:** Configure your server with a valid SSL/TLS certificate. Astro and modern deployments have built-in support for HTTPS.
* **Don't Do This:** Serve sensitive data over HTTP.
* **Encrypt Sensitive Data at Rest:** Encrypt sensitive data stored in databases or files.
* **Do This:** Use a database encryption feature or a library like "crypto" to encrypt data before storing it.
* **Don't Do This:** Store sensitive data in plain text or use weak encryption algorithms.
### Example: Encryption with "crypto"
"""javascript
import crypto from 'crypto';
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32); // Generate a secure key
const iv = crypto.randomBytes(16); // Generate a secure initialization vector
function encrypt(text) {
let cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') };
}
function decrypt(encryptedData, iv) {
let ivBuffer = Buffer.from(iv, 'hex');
let encryptedText = Buffer.from(encryptedData, 'hex');
let decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), ivBuffer);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
const data = 'Sensitive data to encrypt';
const encrypted = encrypt(data);
const decrypted = decrypt(encrypted.encryptedData, encrypted.iv);
console.log('Encrypted:', encrypted);
console.log('Decrypted:', decrypted);
"""
*Why: Encryption protects sensitive data from unauthorized access, even if the storage medium is compromised.*
* **Sanitize and Validate Data:** Always sanitize and validate data before storing it to prevent injection attacks.
* **Do This:** Use prepared statements or parameterized queries to prevent SQL injection attacks. Validate data types and formats to prevent other types of injection attacks.
* **Don't Do This:** Directly concatenate user input into database queries or trust client-side validation.
## 6. Monitoring and Logging
Implement robust monitoring and logging to detect and respond to security incidents.
* **Log Security Events:** Log all security-related events, such as authentication attempts, authorization failures, and suspicious activity.
* **Do This:** Use a logging library to record security events in a structured format. Include timestamps, user IDs, IP addresses, and other relevant information.
* **Monitor Logs for Anomalies:** Continuously monitor logs for suspicious patterns and anomalies.
* **Do This:** Use a security information and event management (SIEM) system or other log analysis tools.
* **Don't Do This:** Ignore or infrequently review logs.
* **Implement Alerting:** Set up alerts for critical security events.
* **Do This:** Configure alerts to notify security personnel when suspicious activity is detected.
* **Don't Do This:** Rely solely on manual log reviews.
### Example: Logging with "console" and potentially a more complex logging system
"""javascript
// console.log is a basic logging primitive.
console.log("[${new Date().toISOString()}] User login attempt from IP: ${Astro.clientAddress}");
console.warn("[${new Date().toISOString()}] Suspicious activity detected for user ${userId}");
console.error("[${new Date().toISOString()}] Authentication failure for user ${userId}");
"""
Utilize more complete logging methodologies, with error alerts, for production applications.
*Why: Monitoring and logging provide visibility into your application's security posture, allowing you to quickly detect and respond to security incidents to prevent malicious users from gaining access.*
## 7. Dependency Management
Vulnerable dependencies are a common source of security vulnerabilities.
* **Keep Dependencies Up-to-Date:** Regularly update your dependencies to the latest versions.
* **Do This:** Use tools like "npm update" or "yarn upgrade" to update your dependencies. Automate dependency updates with tools like Dependabot.
* **Don't Do This:** Ignore dependency updates or postpone applying security patches.
* **Use Dependency Scanning:** Scan your dependencies for known vulnerabilities.
* **Do This:** Use tools like "npm audit" or "yarn audit" to scan your dependencies for vulnerabilities.
* **Don't Do This:** Only rely on manual dependency reviews.
### Example: Using "npm audit"
"""bash
npm audit
"""
*Why: Regularly updating and auditing dependencies reduces the risk of incorporating known vulnerabilities into your application.*
## 8. Astro-Specific Security Considerations
These security considerations are unique to Astro projects.
* **Server-Side Rendering (SSR) Security:** When using SSR, be extra careful with user input. Ensure all user-provided data is properly sanitized and validated before being rendered on the server. Similar XSS issues can exist server-side.
* **API Endpoint Security:** Properly authenticate and authorize access to your API endpoints if you're building an API with Astro. Securely manage API keys and tokens.
* **Environment Variables:** Store sensitive configuration data (e.g., API keys, database passwords) in environment variables, not directly in your code.
* **Do This:** Use ".env" files for local development and configure environment variables in your deployment environment.
### Example: Accessing Environment Variables
"""javascript
const apiKey = import.meta.env.API_KEY;
if (!apiKey) {
console.error('API Key is not defined in environment variables.');
}
"""
*Why: Storing sensitive data in environment variables prevents it from being accidentally exposed in your codebase or version control system.*
## 9. Common Anti-Patterns
* **Ignoring Security Warnings:** Failing to address security warnings from linters, dependency scanners, or other security tools.
* **Assuming User Input is Safe:** Trusting user input without proper validation and sanitization.
* **Hardcoding Secrets:** Embedding API keys, passwords, or other secrets directly in your code.
* **Failing to Follow Best Security Practices:** Failing to follow industry-standard security practices such as using strong password hashing, implementing CSRF protection, and using HTTPS.
* **Not testing security:** A lot of the items listed can be tested via automated workflows and unit tests. Not testing increases the risk of missed malicious code.
## 10. Performance Considerations
Security implementations can impact performance, so balance security and performance.
* **Caching:** Cache authenticated and authorized responses whenever possible to reduce the load on your authentication and authorization systems.
* **Rate Limiting:** Implement rate limiting to prevent denial-of-service attacks and brute-force attacks.
* **Asynchronous operations:** Where possible, implement security functionalities asynchronously to avoid blocking the main thread and hindering performance.
## 11. Conclusion
By following these security best practices, you can significantly enhance the security of your Astro projects and protect your users and data. Remember that security is an ongoing process, and you should continuously review and update your security measures to address new threats and vulnerabilities.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Component Design Standards for Astro This document outlines the component design standards for Astro projects. These standards promote maintainability, reusability, performance, and a consistent development experience across teams. It leverages the latest Astro features and best practices. ## 1. Component Architecture ### 1.1 Modularity and Reusability **Standard:** Components should be designed as independent, reusable units of functionality. **Do This:** * Create components with a single, well-defined responsibility. * Favor composition over inheritance. Use Astro's component slot and props functionalities to allow customization. * Store reusable components in a dedicated directory (e.g., "src/components"). **Don't Do This:** * Create monolithic components that handle multiple unrelated tasks. * Duplicate code across components. Extract common logic into shared components or utility functions. **Why:** Modular components are easier to understand, test, and maintain. Reusable components reduce code duplication and development time. **Example:** """astro // src/components/Button.astro --- interface Props { variant?: 'primary' | 'secondary'; size?: 'small' | 'medium' | 'large'; text: string; onClick?: () => void; } const { variant = 'primary', size = 'medium', text, onClick } = Astro.props; const buttonClasses = { primary: 'bg-blue-500 hover:bg-blue-700 text-white', secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800 border border-gray-400', }; const sizeClasses = { small: 'px-2 py-1 text-sm', medium: 'px-4 py-2 text-base', large: 'px-6 py-3 text-lg', }; const selectedVariantClasses = buttonClasses[variant]; const selectedSizeClasses = sizeClasses[size]; --- <button class:list={[selectedVariantClasses, selectedSizeClasses, 'font-bold rounded']}> {text} </button> """ """astro // src/pages/index.astro --- import Button from '../components/Button.astro'; --- <Button variant="primary" size="medium" text="Click me!" onClick={() => alert('Clicked!')} /> <Button variant="secondary" size="small" text="Cancel" /> """ ### 1.2 Component Grouping **Standard:** Group related components into directories based on feature or domain. **Do This:** * Create directories for specific features or sections of the application (e.g., "src/components/blog", "src/components/ui"). * Include an "index.js" or "index.ts" file in each directory to export all components from that directory. **Don't Do This:** * Dump all components into a single "src/components" directory without any organization. * Create deeply nested directory structures that are difficult to navigate. **Why:** Logical component grouping improves code organization, maintainability, and discoverability. **Example:** """ src/components/ ├── blog/ │ ├── ArticleCard.astro │ ├── ArticleList.astro │ └── index.ts // Exports ArticleCard and ArticleList ├── ui/ │ ├── Button.astro │ ├── Input.astro │ └── index.ts // Exports Button and Input └── index.ts // Exports all components blog and ui """ """typescript // src/components/blog/index.ts export { default as ArticleCard } from './ArticleCard.astro'; export { default as ArticleList } from './ArticleList.astro'; """ """typescript // src/components/index.ts export * from './blog'; export * from './ui'; """ ### 1.3 Data Fetching Responsibilities **Standard:** Limit component data fetching responsibilities. **Do This:** * Fetch data in layouts or pages and pass the data as props to components. This decouples components from specific data sources. * Use [Astro.glob](https://docs.astro.build/en/reference/api-reference/#astroglob) or the [Content Collections API](https://docs.astro.build/en/guides/content-collections/) for data fetching within "src/pages" and "src/layouts". * When absolutely necessary to fetch data within a component, isolate the data fetching logic into a separate helper function or custom hook. **Don't Do This:** * Perform complex data transformations or business logic within components. * Fetch the same data multiple times in different components. **Why:** Decoupling data fetching improves component reusability and testability. Centralized data fetching also improves performance by minimizing redundant requests. **Example:** """astro // src/pages/blog.astro --- import ArticleCard from '../components/ArticleCard.astro'; import { getCollection } from 'astro:content'; const articles = await getCollection('blog'); --- <main> <h1>Blog</h1> <ul> {articles.map((article) => ( <li> <ArticleCard article={article} /> </li> ))} </ul> </main> """ """astro // src/components/ArticleCard.astro --- interface Props { article: { slug: string; data: { title: string; description: string; }; }; } const { article } = Astro.props; --- <a href={"/blog/${article.slug}"}> <h2>{article.data.title}</h2> <p>{article.data.description}</p> </a> """ ## 2. Component Implementation Details ### 2.1 Prop Types **Standard:** Define explicit prop types for all components using TypeScript. **Do This:** * Create an "interface" named "Props" to define the types of all props. * Enforce required props using TypeScript's type system. * Provide default prop values for optional props where appropriate. * Leverage [Astro.props](https://docs.astro.build/en/reference/api-reference/#astroprops) for type safety **Don't Do This:** * Skip prop type definitions. * Use "any" or "unknown" for prop types. * Define default prop values using JavaScript's "||" operator, which can lead to unexpected behavior with falsy values. **Why:** Explicit prop types improve code readability, prevent runtime errors, and improve the developer experience. **Example:** """astro // src/components/Alert.astro --- interface Props { type: 'success' | 'warning' | 'error' | 'info'; message: string; closable?: boolean; } const { type, message, closable = false } = Astro.props; const alertClasses = { success: 'bg-green-100 border-green-400 text-green-700', warning: 'bg-yellow-100 border-yellow-400 text-yellow-700', error: 'bg-red-100 border-red-400 text-red-700', info: 'bg-blue-100 border-blue-400 text-blue-700', }; const selectedClasses = alertClasses[type]; --- <div class:list={[selectedClasses, 'border rounded p-4 mb-4 flex items-center justify-between']}> <span>{message}</span> {closable && ( <button aria-label="Close"> <svg class="w-4 h-4 fill-current" viewBox="0 0 20 20"><path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"/></svg> </button> )} </div> """ ### 2.2 Component Styling **Standard:** Use a consistent styling approach across all components. **Do This:** * Favor component-scoped styles using Astro's built-in CSS support. * Use a CSS preprocessor (e.g., Sass, Less) for complex styling requirements. * Use a utility-first CSS framework (e.g., Tailwind CSS) for consistent styling and rapid development. Install Tailwind using "astro add tailwind". * For dynamic styling, use the ":global()" selector sparingly and only when necessary for interacting with third-party libraries or CSS frameworks. * Consider using CSS variables for theming. **Don't Do This:** * Use inline styles directly in the HTML templates. * Use global styles that can conflict with other components. * Mix different styling approaches within the same project. **Why:** Consistent styling improves code maintainability, reduces visual inconsistencies, and streamlines the development process. **Example (Tailwind CSS):** """astro // src/components/Card.astro --- interface Props { title: string; description: string; } const { title, description } = Astro.props; --- <div class="bg-white rounded-lg shadow-md p-4"> <h2 class="text-xl font-bold mb-2">{title}</h2> <p class="text-gray-700">{description}</p> </div> """ ### 2.3 Component State **Standard:** Minimize component state and use it judiciously. **Do This:** * Prefer stateless functional components whenever possible. * When state is necessary, use Astro's client-side directives ("client:load", "client:idle", "client:visible", "client:media") combined with a front-end framework like Solid, React or Vue to manage component state. * Consider using a global state management library (e.g., Context API with React, Zustand or Jotai) for complex application state. **Don't Do This:** * Store unnecessary data in component state. * Mutate component state directly without using the appropriate setter functions or framework mechanisms. * Overuse global state management for simple component-specific state. **Why:** Minimizing component state improves performance and reduces complexity. Using appropriate state management techniques improves data consistency and prevents unexpected side effects. **Example (SolidJS with Astro):** """astro --- // src/components/Counter.astro --- <Counter client:visible /> <script> import { render } from 'solid-js/web'; import { createSignal } from 'solid-js'; function Counter() { const [count, setCount] = createSignal(0); return ( <div> <p>Count: {count()}</p> <button onClick={() => setCount(count() + 1)}>Increment</button> </div> ); } render(Counter, document.querySelector('counter')) </script> """ ### 2.4 Naming Conventions **Standard:** Follow consistent naming conventions for components and props. **Do This:** * Use PascalCase for component filenames (e.g., "MyComponent.astro"). * Use camelCase for prop names (e.g., "myProp", "onClick"). * Use descriptive and concise names that clearly indicate the component's purpose. * Use plural names for components that display a list of items (e.g., "ArticleList"). **Don't Do This:** * Use inconsistent naming conventions across the project. * Use abbreviations or acronyms that are not widely understood. * Use generic names that do not accurately reflect the component's functionality. **Why:** Consistent naming improves code readability and makes it easier to understand the purpose of components and props. ## 3. Performance Optimization ### 3.1 Component Hydration **Standard:** Use Astro's partial hydration features judiciously to optimize performance. **Do This:** * Use the "client:" directives ("client:load", "client:idle", "client:visible", "client:media") to control when components are hydrated. * Start with "client:idle" or "client:visible" for most interactive components to defer hydration until the browser is idle or the component is visible in the viewport. * Use "client:load" only for components that are critical to the initial user experience. * Consider using "<astro-island>" for even more granular control over hydration. **Don't Do This:** * Hydrate all components on page load, which can negatively impact performance. * Use "client:only" unless you are absolutely sure that the component will only ever be rendered on the client-side. **Why:** Partial hydration significantly improves page load performance by only hydrating the components that need to be interactive. **Example:** """astro // src/components/InteractiveComponent.astro --- // This component will be hydrated when it becomes visible in the viewport. --- <div client:visible> {/* Interactive content here */} </div> """ ### 3.2 Image Optimization **Standard:** Optimize images for performance using Astro's built-in image optimization features or a third-party library. **Do This:** * Use the "<Image />" component from "@astrojs/image" to automatically optimize images. * Specify the "width" and "height" props for all images to prevent layout shifts. * Use the "format" prop to convert images to modern formats like WebP or AVIF. * Use the "quality" prop to control the level of compression. * Consider using a CDN (Content Delivery Network) to serve images from a location closer to the user. **Don't Do This:** * Use large, unoptimized images directly in the HTML templates. * Skip specifying the "width" and "height" props for images. * Serve images from the same server as the application, which can negatively impact performance. **Why:** Image optimization reduces image file sizes, improves page load times, and enhances the user experience. **Example:** """astro // src/components/HeroImage.astro --- import { Image } from '@astrojs/image/components'; import heroImage from '../assets/hero.jpg'; --- <Image src={heroImage} alt="Hero Image" width={1200} height={600} format="webp" quality={80} /> """ ### 3.3 Lazy Loading **Standard:** Use lazy loading for images and other resources that are not immediately visible in the viewport. **Do This:** * Use the "loading="lazy"" attribute for images to defer loading until they are near the viewport. * Consider using a third-party library or custom implementation for lazy loading other types of resources, such as iframes or videos. **Don't Do This:** * Lazy load resources that are above the fold or critical to the initial user experience. **Why:** Lazy loading improves page load performance by deferring the loading of non-critical resources until they are needed. **Example:** """astro <img src="/path/to/image.jpg" alt="Description of image" loading="lazy"> """ ## 4. Security Considerations ### 4.1 Input Sanitization **Standard:** Sanitize user inputs in components to prevent cross-site scripting (XSS) attacks. **Do This:** * Use a sanitization library (e.g., DOMPurify) to sanitize user inputs before rendering them in the HTML templates. * Escape HTML entities in user inputs using Astro's built-in templating engine features. **Don't Do This:** * Render user inputs directly in the HTML templates without any sanitization or escaping. **Why:** Input sanitization prevents malicious code from being injected into the application, which can compromise user data and security. **Example:** """astro // Example using DOMPurify --- import DOMPurify from 'dompurify'; interface Props { userInput: string; } const { userInput } = Astro.props; // Sanitize the user input const sanitizedInput = DOMPurify.sanitize(userInput); --- <p set:html={sanitizedInput}></p> """ ### 4.2 Avoid Exposing Sensitive Data **Standard:** Avoid exposing sensitive data in client-side components. **Do This:** * Store sensitive data on the server-side. * Use environment variables to store API keys and other sensitive configuration values. Utilize the "import.meta.env" to access environment variables for the build **Don't Do This:** * Include API keys or other sensitive data directly in client-side components. * Expose sensitive data in the HTML source code. **Why:** Protecting sensitive data prevents unauthorized access and protects user privacy. **Example:** """astro --- // Access the API key from an environment variable const apiKey = import.meta.env.PUBLIC_API_KEY; --- // Use the API key to fetch data from the server """ ## 5. Testing ### 5.1 Unit Testing **Standard:** Write unit tests for individual components to ensure they function correctly. **Do This:** * Use a testing framework (e.g., Jest, Mocha) to write unit tests. * Test the component's props, state, and rendering behavior. * Use mocking to isolate components from external dependencies. **Don't Do This:** * Skip unit tests for complex components. * Write unit tests that are tightly coupled to the component's implementation details. **Why:** Unit tests improve code quality, prevent regressions, and make it easier to refactor code. ### 5.2 Component Integration tests **Standard:** Write integration tests to ensure components interact correctly in a larger system. **Do This:** * Use testing libraries (e.g., Playwright, Cypress) for end-to-end tests to verify that components render and function correctly in the browser. * Verify that data is passed to components in an intended manor. **Don't Do This:** * Neglect to test component collaboration. * Write only unit tests, and don't test real life rendering behavior. **Why:** Unit and Integration testing are essential for ensuring that components work in intended ways. ## 6. Documentation ### 6.1 Component Documentation **Standard:** Document component usage and functionality clearly. **Do This:** * Include a JSDoc-style comment block at the top of each component file that describes the component's purpose, props, and usage. * Use a documentation generator (e.g., Storybook) to create a living style guide for the component library. **Don't Do This:** * Skip documenting component usage and functionality * Make hard to maintain documentation. Keep documentation with the components themselves. **Why:** Documentation improves code understandability, facilitates collaboration, and makes it easier to maintain the component library. **Example:** """astro /** * A reusable button component. * * @param {string} text - The text to display on the button. * @param {'primary' | 'secondary'} variant - The button variant (optional, defaults to 'primary'). * @param {() => void} onClick - The function to call when the button is clicked (optional). */ interface Props { text: string; variant?: 'primary' | 'secondary'; onClick?: () => void; } """ These coding standards are a living document and should be updated as new versions of Astro are released and new best practices emerge. By following these standards, development teams can build high-quality, maintainable, and performant Astro applications.
# Core Architecture Standards for Astro This document outlines the core architectural standards for Astro projects. These standards are designed to promote maintainability, scalability, performance, and security. Following these guidelines will help ensure consistency across projects and facilitate collaboration among developers. This document assumes familiarity with Astro's fundamental concepts. Focus will be on using the newest features and avoiding legacy patterns. ## 1. Project Structure and Organization A well-defined project structure is crucial for maintainability and scalability. Consistency in how we organize our code lets us navigate any Astro project quickly. **Standards:** * **Do This:** Adhere to a modular and component-based architecture. Organize code into reusable components. * **Don't Do This:** Create monolithic structures that intertwine functionality, making components difficult to reuse and test. * **Why:** Modularity promotes separation of concerns, improving code readability, testability, and reusability. Component-based structure is fundamental to Astro's design. ### 1.1 "src/" Directory The "src/" directory is the heart of an Astro project, containing all the project's source code. **Standards:** * **Do This:** Structure the "src/" directory with the following subdirectories: * "components/": Reusable UI components (Astro components, React, Vue, Svelte, etc.). * "layouts/": Page layouts. * "pages/": Routes and page definitions. Each ".astro" file here represents a page. * "content/": Content collections for structured data (blog posts, documentation, etc.). * "utils/": Utility functions. * "styles/": Global styles and themes. * "scripts/": Client-side JavaScript. * **Don't Do This:** Place all code directly into the "src/" directory or create an unstructured mess. Also, avoid inconsistent naming conventions for directories. * **Why:** A predictable structure improves navigation and code discoverability. **Example:** """ astro-project/ ├── src/ │ ├── components/ │ │ ├── Card.astro │ │ └── Button.jsx │ ├── layouts/ │ │ ├── BaseLayout.astro │ │ └── BlogPostLayout.astro │ ├── pages/ │ │ ├── index.astro │ │ ├── about.astro │ │ └── blog/[slug].astro │ ├── content/ │ │ ├── blog/ │ │ │ ├── first-post.mdx │ │ │ └── second-post.mdx │ │ └── config.ts │ ├── utils/ │ │ ├── date-formatter.ts │ │ └── api-client.js │ ├── styles/ │ │ ├── global.css │ │ └── theme.css │ └── scripts/ │ └── app.js ├── astro.config.mjs ├── package.json └── tsconfig.json """ ### 1.2 Component Organization Consistent component structure and naming are essential. **Standards:** * **Do This:** * Use PascalCase for component filenames (e.g., "MyComponent.astro"). * Group related components in directories within "src/components/". * Create a "index.astro" or "index.jsx" file within component directories to export the main component, allowing for shorter import paths. * **Don't Do This:** Use inconsistent naming schemes or scatter components throughout the project. * **Why:** Improves readability and simplifies component imports. **Example:** """ src/components/ ├── Blog/ │ ├── BlogPostCard.astro │ ├── BlogPostList.astro │ └── index.astro // Exports BlogPostList & BlogPostCard └── UI/ ├── Button.jsx ├── Input.jsx └── index.jsx // Exports Button & Input """ **Import Example:** """astro --- import { BlogPostList } from '@components/Blog'; //Importing from the Blog component --- <BlogPostList /> """ ### 1.3 Content Collections Astro's content collections are a powerful way to manage structured content. **Standards:** * **Do This:** * Use content collections for blog posts, documentation, and other structured data. * Define schemas for your content collections using Zod to ensure data consistency. * Utilize the "getCollection()" function to query content within your components. * **Don't Do This:** Hardcode content directly into components or forgo defining schemas for collections. * **Why:** Provides data validation and type safety, improving content management. **Example:** "src/content/config.ts" """typescript import { defineCollection, z } from 'astro:content'; const blogCollection = defineCollection({ schema: z.object({ title: z.string(), date: z.date(), author: z.string(), draft: z.boolean().default(false), tags: z.array(z.string()).optional(), description: z.string(), image: z.string().optional() }), }); export const collections = { 'blog': blogCollection, }; """ "src/pages/blog/[slug].astro" """astro --- import { getCollection, getEntryBySlug } from 'astro:content'; import { format } from 'date-fns'; export async function getStaticPaths() { const blogPosts = await getCollection('blog'); return blogPosts.map((post) => ({ params: { slug: post.slug }, props: { post }, })); } const { post } = Astro.props; const { Content } = await post.render(); --- <html lang="en"> <head> <title>{post.data.title}</title> </head> <body> <article> <h1>{post.data.title}</h1> <p>Published on: {format(post.data.date, 'MMMM dd, yyyy')}</p> <Content /> </article> </body> </html """ ## 2. Component Design and Implementation Effective component design is fundamental to building maintainable Astro applications. **Standards:** * **Do This:** Follow the principles of single responsibility, separation of concerns, and loose coupling when designing components. * **Don't Do This:** Create overly complex components or ones with dependencies that are hard to manage. * **Why:** Facilitates code maintenance, testing, and reuse. ### 2.1 Astro Component Best Practices Astro components should be self-contained and reusable. **Standards:** * **Do This:** * Use props to pass data into components. Use prop destructuring for improved readability. * Utilize the client directives ("client:load", "client:idle", "client:visible", "client:only") to optimize JavaScript loading and execution. SSR first. * Use Slots to allow users of your components to inject HTML and other components. * **Don't Do This:** Directly modify global state within components or tightly couple components to specific data sources. * **Why:** Promotes component reusability and testability. **Example:** """astro --- // src/components/Card.astro interface Props { title: string; body: string; href: string; } const { title, body, href } = Astro.props; --- <a href={href} class="card"> <h3> {title} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> </svg> </h3> <p>{body}</p> </a> <style> .card { /* Styles... */ } </style> """ """astro // src/pages/index.astro --- import Card from '../components/Card.astro'; --- <Card title="Astro" body="Learn more about Astro" href="https://astro.build/" /> """ ### 2.2. Using Client Directives Astro's client directives control when and how JavaScript is loaded and executed. **Standards:** * **Do This:** * Use "client:load" for components that need to be interactive as soon as possible. * Use "client:idle" for components that can wait until the browser is idle. * Use "client:visible" for components that should load when they become visible in the viewport. * Use "client:only" *sparingly* and only when necessary for UI frameworks that are not server-renderable at all. * Default to server-side rendering when interactivity isn't required. * **Don't Do This:** Load all JavaScript eagerly using "client:load" if it is not necessary (hurts performance). * **Why:** Optimizes page load performance by deferring the loading of non-critical JavaScript. **Example:** """jsx // src/components/Counter.jsx import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } """ """astro // src/pages/index.astro --- import Counter from '../components/Counter.jsx'; --- <Counter client:idle /> """ ### 2.3 Styling Components Use modular and maintainable styling approaches. **Standards:** * **Do This:** * Use CSS modules, styled components, or utility-first CSS frameworks (Tailwind CSS) for component-specific styling. * Use global CSS files ("src/styles/global.css") for base styles and theming. * Leveraging Astro's built-in CSS scoping for ".astro" components. * **Don't Do This:** Write inline styles directly into components (except for highly specific, dynamic cases) or pollute the global namespace with un-scoped CSS. * **Why:** Improves style isolation, reusability, and maintainability. **Example (CSS Modules):** """jsx // src/components/Button.jsx import styles from './Button.module.css'; export default function Button({ children, onClick }) { return ( <button className={styles.button} onClick={onClick}> {children} </button> ); } """ """css /* src/components/Button.module.css */ .button { background-color: #007bff; color: white; padding: 10px 20px; border: none; cursor: pointer; } .button:hover { background-color: #0056b3; } """ **Example (Tailwind CSS):** """jsx // src/components/Button.jsx export default function Button({ children, onClick }) { return ( <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" onClick={onClick}> {children} </button> ); } """ **Example (Astro Component scoping):** """astro --- // src/components/MyComponent.astro --- <div class='container'> <p>Styled paragraph</p> </div> <style> .container { border: 1px solid red; } p { color: blue; } </style> """ The CSS rules are scoped to the "<MyComponent>" component. ## 3. Data Fetching and Management Efficient data fetching and management are critical for performance. **Standards:** * **Do This:** Use Astro's built-in "fetch()" API or other data-fetching libraries (e.g., "axios", "ky") to retrieve data. Use content collections for local data. Implement caching strategies to avoid unnecessary requests. * **Don't Do This:** Perform data fetching directly within client-side components (except when absolutely necessary) or neglect implementing caching. * **Why:** Improves performance and reduces network load. ### 3.1 Server-Side Data Fetching Where possible, fetch data on the server. **Standards:** * **Do This:** Fetch data within "getStaticPaths()" or directly in your Astro component's frontmatter when possible. Use environment variables to securely store API keys. * **Don't Do This:** Expose API keys directly in your client-side code. * **Why:** Prevents exposing sensitive data to the client and improves security. **Example:** """astro --- // src/pages/index.astro import { getPosts } from '../utils/api-client'; const posts = await getPosts(); --- <ul> {posts.map((post) => ( <li>{post.title}</li> ))} </ul> """ """javascript // src/utils/api-client.js const API_URL = import.meta.env.API_URL; export async function getPosts() { const res = await fetch("${API_URL}/posts"); const data = await res.json(); return data; } """ ### 3.2 Caching Strategies Effective caching can significantly improve performance. **Standards:** * **Do This:** Utilize HTTP caching headers, service workers, or in-memory caching to store frequently accessed data. * **Don't Do This:** Neglect implementing caching mechanisms. * **Why:** Reduces network requests and improves response times. **Example (HTTP Caching):** Set appropriate "Cache-Control" headers on your API responses. """javascript //Example Node.js server app.get('/api/posts', (req, res) => { res.set('Cache-Control', 'public, max-age=3600'); // Cache for 1 hour // code to fetch and send posts }); """ ## 4. Error Handling and Logging Robust error handling and logging are essential for tracking down bugs in production **Standards:** * **Do This:** Implement comprehensive error handling mechanisms using "try...catch" blocks and Astro's "onError" hook. Implement a logging strategy using a service like Sentry or a custom logging mechanism. * **Don't Do This:** Allow errors to go unhandled or neglect implementing logging. * **Why:** Improves application resilience and helps diagnose issues. ### 4.1 Error Boundaries Using error boundaries within components to catch errors and present fallback UI. Note that framework-specific boundaries ("React.ErrorBoundary", "Vue's onErrorCaptured") can be leveraged with Astro islands. **Example:** """jsx // src/components/ErrorBoundary.jsx (React Example) import React, { Component } from 'react'; class ErrorBoundary extends Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error("Caught error: ", error, errorInfo); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } } export default ErrorBoundary; """ """astro // src/pages/index.astro --- import ErrorBoundary from '../components/ErrorBoundary.jsx'; import MyComponent from '../components/MyComponent.jsx'; --- <ErrorBoundary client:visible> <MyComponent client:visible /> </ErrorBoundary> """ ### 4.2 Logging Implement a comprehensive logging strategy. **Standards:** * **Do This:** Use a logging service like Sentry or implement a custom logging mechanism. Log errors, warnings, and informational messages. Use environment variables to configure logging levels. * **Don't Do This:** Log sensitive information or neglect implementing logging. * **Why:** Helps diagnose issues and track application health. **Example (Sentry):** """javascript // src/scripts/sentry.js import * as Sentry from "@sentry/browser"; Sentry.init({ dsn: import.meta.env.SENTRY_DSN, integrations: [ new Sentry.BrowserTracing(), new Sentry.Replay() ], // Performance Monitoring tracesSampleRate: 0.1, // Capture 10% of transactions for performance monitoring. // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to lower it at first. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. }); """ """astro // src/layouts/BaseLayout.astro --- import '../scripts/sentry.js';//Initialize sentry in a base layout --- """ ## 5. Accessibility (a11y) Building accessible websites is a crucial aspect of inclusive design. **Standards:** * **Do This:** Follow accessibility guidelines like WCAG (Web Content Accessibility Guidelines). Use semantic HTML, provide alternative text for images, and ensure sufficient color contrast. Test your website with accessibility tools like Axe. * **Don't Do This:** Neglect accessibility considerations or create websites that are difficult for users with disabilities to navigate. * **Why:** Ensures that your website is usable by everyone, regardless of their abilities. Also, good for SEO. ### 5.1 Semantic HTML Use semantic HTML elements to provide structure and meaning to your content. **Standards:** * **Do This:** Use elements like "<article>", "<nav>", "<aside>", "<header>", "<footer>", "<main>", and "<section>" appropriately. * **Don't Do This:** Use generic "<div>" elements for everything. * **Why:** Improves accessibility and SEO. **Example:** """astro <header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/blog">Blog</a></li> </ul> </nav> </header> <main> <article> <h1>My Blog Post</h1> <p>Content of the blog post.</p> </article> </main> <footer> <p>© 2024 My Website</p> </footer> """ ### 5.2 ARIA Attributes Use ARIA (Accessible Rich Internet Applications) attributes to enhance accessibility. **Standards:** * **Do This:** Use ARIA attributes to provide additional information to assistive technologies when semantic HTML is not sufficient. * **Don't Do This:** Overuse ARIA attributes or use them incorrectly. * **Why:** Improves accessibility for users with disabilities. **Example:** """jsx // src/components/CustomButton.jsx export default function CustomButton({ onClick, children, ariaLabel }) { return ( <button onClick={onClick} aria-label={ariaLabel}> {children} </button> ); } """ ### 5.3 Image Alt Text Provide alternative text for images to describe their content. **Standards:** * **Do This:** Add "alt" attributes to all "<img>" elements. The "alt" text should be concise and descriptive. * **Don't Do This:** Leave "alt" attributes empty or use generic descriptions like "image". * **Why:** Improves accessibility for users who cannot see the images. **Example:** """astro <img src="/images/my-image.jpg" alt="A beautiful sunset over the ocean" /> """ ## 6. Security Security is paramount. Adhere to security best practices to protect your application and users. **Standards:** * **Do This:** Sanitize user inputs, use HTTPS, protect against Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) attacks. Use secure coding practices. * **Don't Do This:** Store sensitive information in client-side code or neglect implementing security measures. * **Why:** Protects your application and users from security vulnerabilities and attacks. ### 6.1 Input Sanitization Sanitize user inputs to prevent XSS attacks. **Standards:** * **Do This:** Use a library like "DOMPurify" to sanitize user inputs before rendering them in your Astro components. * **Don't Do This:** Directly render user inputs without sanitization. * **Why:** Prevents attackers from injecting malicious scripts into your application. **Example:** """javascript // src/utils/sanitize.js import DOMPurify from 'dompurify'; export function sanitize(html) { return DOMPurify.sanitize(html); } """ """astro --- // src/pages/index.astro import { sanitize } from '../utils/sanitize'; const userInput = "<script>alert('XSS');</script>Hello!"; const sanitizedInput = sanitize(userInput); --- <p set:html={sanitizedInput} /> """ ### 6.2 HTTPS Always use HTTPS to encrypt communication between the client and server. **Standards:** * **Do This:** Configure your server to use HTTPS. Obtain an SSL/TLS certificate from a trusted certificate authority. * **Don't Do This:** Use HTTP in production. * **Why:** Protects data in transit from eavesdropping and tampering. ### 6.3 Environment Variables Never directly expose sensitive information (API keys, database passwords, etc.) in your client-side code. **Standards:** * **Do This:** Store all sensitive information in environment variables. Access environment variables using "import.meta.env". Ensure ".env" files are not committed to source control. * **Don't Do This:** Hardcode sensitive information in your code directly. * **Why:** prevents sensitive information from being exposed. **Example:** """javascript // .env API_KEY=your_api_key DATABASE_URL=your_database_url """ """javascript // src/utils/api-client.js const API_KEY = import.meta.env.API_KEY; export async function fetchData() { const res = await fetch("/api/data?apiKey=${API_KEY}"); const data = await res.json(); return data; } """ These core architecture standards for Astro are designed to enable the creation of high-quality, maintainable, and scalable applications. Adherence to these standards will ensure consistency, improve collaboration, and contribute to the long-term success of Astro projects.
# Performance Optimization Standards for Astro This document outlines the coding standards and best practices for performance optimization within Astro projects. Following these guidelines will lead to faster, more responsive, and resource-efficient web applications. These standards are tailored to the latest Astro features and ecosystem. ## 1. Architectural Considerations Choosing the correct architecture and approach upfront is critical for performance. ### 1.1. Islands Architecture **Standard:** Leverage Astro's Islands Architecture to minimize JavaScript execution on the client. Each interactive component (island) should be isolated and loaded only when needed. **Why:** Islands Architecture reduces the amount of JavaScript that needs to be downloaded, parsed, and executed by the browser, leading to faster initial load times and improved Time to Interactive (TTI). **Do This:** * Identify genuinely interactive components that require client-side JavaScript. * Render static content as HTML without client-side hydration. * Use Astro's "client:" directives ("client:load", "client:visible", "client:idle", "client:only") to control when islands are loaded. **Don't Do This:** * Hydrate entire pages or large sections of static content with client-side JavaScript. * Load all JavaScript upfront, regardless of whether it's needed. * Use "client:load" for components that are initially below-the-fold. It will improve the initial paint, but delay the Time to Interactive. **Example:** """astro --- // MyComponent.astro --- <div class="my-component"> <p>This is a static paragraph.</p> <MyInteractiveComponent client:visible /> </div> """ """jsx // MyInteractiveComponent.jsx (or .tsx) import React, { useState } from 'react'; const MyInteractiveComponent = () => { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default MyInteractiveComponent; """ **Anti-Pattern:** Hydrating the entire "MyComponent" Astro page just to make the button interactive, even though the paragraph is entirely static content. ### 1.2. Static Site Generation (SSG) **Standard:** Pre-render as much content as possible during build time (Static Site Generation). **Why:** SSG delivers static HTML files to the browser, eliminating server-side rendering overhead and resulting in faster page loads. **Do This:** * Fetch data and generate HTML pages during the build process for content that doesn't require real-time updates. * Use Astro's built-in support for Markdown and MDX to create content-rich static sites. **Don't Do This:** * Use Server-Side Rendering (SSR) unnecessarily for content that can be pre-rendered. * Make unoptimised network requests at runtime. **Example:** """astro --- // src/pages/blog/[slug].astro import { getEntryBySlug } from 'astro:content'; export async function getStaticPaths() { const blogEntries = await getCollection('blog'); return blogEntries.map(entry => ({ params: { slug: entry.slug }, props: { entry }, })); } const { entry } = Astro.props; const { Content } = await entry.render(); --- <Layout title={entry.data.title}> <h1>{entry.data.title}</h1> <Content /> </Layout> """ ### 1.3. Content Delivery Network (CDN) **Standard:** Utilize a CDN to serve static assets (images, CSS, JavaScript) from geographically distributed servers. **Why:** CDNs reduce latency by serving content from a server closer to the user's location. **Do This:** * Integrate Astro with a CDN provider like Cloudflare, Netlify CDN, or AWS CloudFront. * Configure your CDN to cache static assets effectively. **Don't Do This:** * Serve static assets directly from your origin server without caching. **Example:** While Astro doesn't directly configure your CDN; it provides a build output that's easily deployed to a CDN. Configure your deployment platform (Netlify, Vercel, Cloudflare Pages, etc.) to leverage its CDN capabilities. For example, on Netlify: 1. Deploy your Astro site to Netlify. 2. Netlify automatically uses its global CDN to serve your static assets. 3. Configure Cache-Control headers within Netlify's settings or within the "netlify.toml" file for detailed cache management. ### 1.4 Routing Strategy **Standard:** Adopt an optimised routing strategy. **Why:** An efficient routing approach ensures swift navigation and content delivery. **Do This:** * Employ dynamic routing for pages with changeable content. * Implement prefetching for anticipated navigation paths. * Strategically utilize client-side routing to achieve seamless transitions. **Don't Do This:** * Overcomplicate routing with excessive redirects. * Rely on solely server-side routing for every internal link. **Example:** """astro // src/pages/products/[id].astro export async function getStaticPaths() { const products = await fetchProductsFromDatabase(); return products.map(product => ({ params: { id: product.id }, })); } const { id } = Astro.params; const product = await fetchProductDetails(id); """ ## 2. Component Optimization Efficient components are critical to overall performance. ### 2.1. Minimize Client-Side JavaScript **Standard:** Reduce the amount of client-side JavaScript. **Why:** Too much JavaScript can significantly slow down page load times and degrade user experience, especially on mobile devices. **Do This:** * Use Astro components for the majority of your UI, which render to HTML by default. * Defer or lazy-load JavaScript only when necessary for interactivity. * Replace large libraries, with smaller, more efficient alternatives. **Don't Do This:** * Import entire JavaScript libraries when only a small portion is needed. * Rely on Client-Side-Rendering (CSR) when SSG or SSR is appropriate. * Use jQuery. **Example:** """astro --- // Bad: Importing the entire Lodash library // import _ from 'lodash'; // Good: Importing only the functions you need import debounce from 'lodash-es/debounce'; const debouncedFunction = debounce(() => { console.log('Debounced function called'); }, 300); --- <input type="text" on:input={debouncedFunction} /> """ ### 2.2. Efficient Data Fetching **Standard:** Optimize data fetching to minimize network requests and reduce data transfer. **Why:** Slow or inefficient data fetching can be a major performance bottleneck. **Do This:** * Use GraphQL or similar technologies to fetch only the data you need. * Implement caching strategies to avoid redundant requests. * Use Astro's "getStaticPaths" to pre-render pages at build time. * Utilize "Astro.fetchContent()" and similar mechanisms to hydrate components directly from Markdown or MDX. **Don't Do This:** * Make multiple unnecessary requests for the same data. * Fetch large amounts of data that are not needed on the page. **Example:** """astro --- // src/pages/blog.astro import { getCollection } from 'astro:content'; export async function getStaticProps() { const posts = await getCollection('blog'); return { props: { posts: posts.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime()), }, }; } const { posts } = Astro.props; --- <ul> {posts.map((post) => ( <li> <a href={"/blog/${post.slug}"}>{post.data.title}</a> </li> ))} </ul> """ ### 2.3. Image Optimization **Standard:** Optimize images to reduce file size without sacrificing quality. **Why:** Large images can significantly slow down page load times. **Do This:** * Use optimized image formats like WebP or AVIF. * Resize images to the appropriate dimensions for their display size. * Use responsive images with the "<picture>" element or "srcset" attribute to serve different image sizes based on screen size. * Lazy load images using the "loading="lazy"" attribute. * Use Astro's built-in image optimization features or integrate with image optimization services. **Don't Do This:** * Use unnecessarily large images. * Serve images in unoptimized formats like BMP or TIFF. * Load all images immediately, even those that are below-the-fold. **Example:** """astro --- // Using Astro's built-in image optimization with the <Image /> component. Requires installing @astrojs/image. import { Image } from '@astrojs/image/components'; import myImage from '../assets/my-image.jpg'; --- <Image src={myImage} alt="My Image" width={600} height={400} format="webp" loading="lazy" /> """ ### 2.4 Component Reusability and Memoization **Standard:** Design components to be reusable and implement memoization techniques to prevent re-renders. **Why:** Reusable components reduce code duplication and improve maintainability. Memoization prevents unnecessary re-renders of components, improving performance. **Do This:** * Create reusable components with well-defined props. * Use "React.memo" or similar memoization techniques to prevent re-renders of pure components. * Use the "useMemo" and "useCallback" hooks in React to memoize expensive calculations and function references. **Don't Do This:** * Create tightly coupled components that are difficult to reuse. * Rely on global state excessively. * Re-render components unnecessarily when their props have not changed. **Example:** """jsx // React Component Memoization import React, { memo } from 'react'; const MyComponent = ({ data }) => { console.log('MyComponent rendered'); return ( <div> {data.map(item => ( <p key={item.id}>{item.name}</p> ))} </div> ); }; export default memo(MyComponent); """ """jsx // React useMemo and useCallback import React, { useState, useMemo, useCallback } from 'react'; function MyComponent({ items }) { const [count, setCount] = useState(0); const expensiveCalculation = useMemo(() => { console.log('Performing expensive calculation'); return items.reduce((sum, item) => sum + item.value, 0); }, [items]); const increment = useCallback(() => { setCount(c => c + 1); }, []); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <p>Expensive Calculation: {expensiveCalculation}</p> </div> ); } export default MyComponent; """ ## 3. Code Optimization Writing efficient code is essential in Astro projects. ### 3.1. Minimize DOM Manipulation **Standard:** Reduce direct DOM manipulation. **Why:** Direct DOM manipulations are resource-intensive. Minimize or batch them for increased performance. **Do This:** * Use Virtual DOM concepts such as those offered by Preact. Memoize components so that they are only re-rendered when required. * Batch updates where needed to prevent layout thrashing. **Don't Do This:** * Directly manage DOM elements where it can be avoided. **Example:** Instead of making individual changes, group them into a single update. While this isn't direct DOM manipulation (best practice!), the principle applies to how components render, and demonstrates the benefit of batching. """js // Update multiple styles at once element.style.cssText = " color: blue; font-size: 16px; font-weight: bold; "; """ ### 3.2. Efficient JavaScript **Standard:** Use efficient JavaScript code. **Why:** Inefficient JavaScript code can lead to performance bottlenecks. **Do This:** * Avoid memory leaks by properly cleaning up event listeners and timers. * Use efficient algorithms and data structures. * Profile your code to identify and optimize performance bottlenecks. * Use the "const" keyword for variables to indicate they will not be reassigned. * Use template literals instead of string concatenation for better readability and performance. * Use arrow functions for concise and efficient function expressions. * Use array methods liek "map", "filter", and "reduce" instead of imperative loops where appropriate. **Don't Do This:** * Write inefficient or unnecessarily complex code. * Use global variables excessively. * Ignore performance warnings from your IDE or linter. **Example:** """javascript // Efficiently calculate the sum of an array of numbers const numbers = [1, 2, 3, 4, 5]; const sum = numbers.reduce((acc, num) => acc + num, 0); console.log(sum); // Output: 15 """ ### 3.3. CSS Optimization **Standard:** Optimize CSS code for performance. **Why:** Inefficient CSS can impact rendering speed and performance. **Do This:** * Minify and compress CSS files. * Remove unused CSS rules. * Use CSS Modules or similar techniques to scope CSS rules and avoid conflicts. * Avoid overly complex CSS selectors. * Use CSS variables for reusable values. * Use "transform"property instead of "top", "right", "bottom", and "left" for animations. **Don't Do This:** * Write overly specific or nested CSS selectors. * Include large, unused CSS files. * Use inline styles excessively. **Example:** """css /* Minified CSS */ body{font-family:sans-serif;margin:0}h1{color:#333} """ ### 3.4 Font Optimization **Standard:** Optimize web fonts for performance. **Why:** Web fonts can significantly impact page load times if not properly optimized. **Do This:** * Use web font formats like WOFF2. * Load fonts asynchronously using "font-display: swap;". * Use font subsets to include only the characters needed on your site. * Host fonts locally to avoid third-party dependencies. **Don't Do This:** * Use too many custom fonts. * Load fonts synchronously, blocking rendering. * Use very large font files. **Example:** """css /* Load font asynchronously */ @font-face { font-family: 'MyFont'; src: url('/fonts/MyFont.woff2') format('woff2'); font-display: swap; } body { font-family: 'MyFont', sans-serif; } """ ## 4. Tooling and Automation Leverage tools and automation to enforce these standards. ### 4.1. Linting and Formatting **Standard:** Use linters and formatters to enforce code style and best practices. **Why:** Linters and formatters help maintain code consistency and identify potential issues early on. **Do This:** * Configure ESLint with recommended Astro and React rules. * Use Prettier to automatically format code. * Integrate linters and formatters into your development workflow using Git hooks or CI/CD pipelines. **Don't Do This:** * Ignore warnings and errors from linters. * Manually format code without using a formatter. **Example:** ".eslintrc.cjs" configuration """javascript module.exports = { env: { browser: true, es2020: true, node: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', ], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, plugins: ['react-refresh'], rules: { 'react-refresh/only-export-components': 'warn', '@typescript-eslint/no-unused-vars': 'warn', // Or "error" in stricter settings 'no-unused-vars': 'warn', 'no-console': 'warn', // Or "error" for production }, settings: { react: { version: 'detect', // Automatically detect the React version }, }, }; """ ### 4.2. Performance Monitoring **Standard:** Implement performance monitoring to track and improve performance over time. **Why:** Performance monitoring helps identify performance regressions and areas for improvement. **Do This:** * Use tools like Google PageSpeed Insights, WebPageTest, or Lighthouse to measure website performance. * Monitor real-user performance using tools like Google Analytics or New Relic. * Set up performance budgets to track and prevent performance regressions. **Don't Do This:** * Ignore performance metrics. * Rely solely on manual testing for performance evaluation. ### 4.3. Automated Testing **Standard:** Use automated tests to prevent performance regressions and ensure code quality. **Why:** Automated tests help catch performance issues early on and ensure that code changes do not negatively impact performance. **Do This:** * Write unit tests, integration tests, and end-to-end tests. * Use performance testing tools to measure the performance of your code. * Integrate tests into your CI/CD pipeline. **Don't Do This:** * Skip writing tests. * Rely solely on manual testing. ## 5. Security Considerations While this document primarily focuses on performance, security and performance are intertwined. ### 5.1. Secure Data Handling **Standard:** Always handle user data securely. **Why:** Securing user data protects privacy and prevents data breaches. **Do This:** * Sanitize and validate all user input to prevent cross-site scripting (XSS) attacks. * Use parameterized queries or ORMs to prevent SQL injection attacks. * Hash passwords using strong hashing algorithms like bcrypt or Argon2. * Use HTTPS to encrypt data in transit. **Don't Do This:** * Store sensitive data in plain text. * Trust user input without validation. ### 5.2 Dependency Management **Standard:** Regularly update dependencies to patch security vulnerabilities. **Why:** Outdated dependencies can contain security vulnerabilities that can be exploited by attackers. **Do This:** * Use a dependency management tool like npm or yarn to manage your project's dependencies. * Regularly update dependencies to the latest versions. * Use a tool like Snyk or Dependabot to automatically identify and fix security vulnerabilities in your dependencies. **Don't Do This:** * Use outdated dependencies. * Ignore security alerts from your dependency management tool. ## Conclusion By adhering to these performance optimization standards, Astro developers can create web applications that are fast, responsive, and resource-efficient. Continuously monitoring and improving performance is essential for delivering a great user experience. This document serves as a comprehensive guide that can be adopted by professional development teams as they build and maintain Astro projects. Continuously review and update these standards as the Astro ecosystem evolves.
# State Management Standards for Astro This document outlines coding standards for state management in Astro projects. These standards aim to promote maintainability, performance, and predictability of data flow throughout your Astro application. They are based on the current best practices for state management within Astro's architecture and ecosystem. ## 1. General Principles of State Management in Astro ### 1.1. Understanding Astro's Architecture and its Impact on State Astro is designed as a "content-focused" web framework. This means its primary goal is to deliver fast, static websites with islands of interactivity, rather than being a Single Page Application (SPA). This philosophy heavily influences how state management should be approached. * **Standard:** Favor build-time data fetching and static rendering wherever possible. * **Why:** Reduces client-side JavaScript, resulting in faster page loads and improved SEO. * **Do This:** Use "getStaticPaths()" and "Astro.glob()" to fetch data during the build process for routes and content. * **Don't Do This:** Fetch data dynamically on the client-side if it can be determined at build time. """astro // src/pages/blog/[slug].astro export async function getStaticPaths() { const posts = await Astro.glob('./posts/*.md'); return posts.map(post => ({ params: { slug: post.frontmatter.slug }, props: { post }, })); } const { post } = Astro.props; """ * **Standard:** Isolate interactive components ("islands") and manage their state independently. * **Why:** Astro's partial hydration model means that only the necessary components become interactive, limiting the scope of client-side state complexity. * **Do This:** Define clear boundaries for interactive components and use appropriate state management mechanisms within each island rather than trying to create one global state. * **Don't Do This:** Over-hydrate components unnecessarily or create a monolithic state object encompassing the entire application, undermining Astro's performance benefits. ### 1.2. Choosing the right State Management approach * **Standard:** Prefer component-local state for simple interactivity. * **Why:** Simplifies development, reduces dependencies, and aligns with Astro's component-centric architecture. * **Do This:** Utilize "useState" from React or similar hooks in your interactive components for basic UI updates and data handling when using a UI framework like React. * **Don't Do This:** Introduce a complex state management library when component-local state is sufficient. """jsx // src/components/Counter.jsx 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; """ * **Standard:** Use Context API (React or Vue) for sharing state within a component subtree. * **Why:** Provides a simple way to avoid prop drilling and share data between related components without external dependencies. * **Do This:** Create a Context provider higher up in the component tree and consume the context within child components that need access to the shared state. * **Don't Do This:** Use Context for global application state if more advanced features like state persistence, complex updates, or computed values are needed. """jsx // src/context/ThemeContext.jsx import React, { createContext, useState, useContext } from 'react'; const ThemeContext = createContext(); export function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } export function useTheme() { return useContext(ThemeContext); } // src/components/ThemeToggle.jsx import React from 'react'; import { useTheme } from '../context/ThemeContext'; function ThemeToggle() { const { theme, toggleTheme } = useTheme(); return ( <button onClick={toggleTheme}> Toggle Theme (Current: {theme}) </button> ); } export default ThemeToggle; """ """astro // src/layouts/Layout.astro --- import { ThemeProvider } from '../context/ThemeContext.jsx'; --- <html lang="en"> <head>...</head> <body> <ThemeProvider> <slot /> </ThemeProvider> </body> </html> """ * **Standard:** Opt for a state management library (e.g., Zustand, Jotai, Recoil, Redux) only when dealing with complex application state, cross-component dependencies, or advanced data manipulation needs. * **Why:** Introduces additional complexity and overhead, so it should be justified by the specific requirements of the project. Consider the bundle size implications. * **Do This:** Carefully evaluate the pros and cons of different libraries and choose one that best suits the scale and complexity of your application. For simpler stores consider Zustand or Jotai. React Context + useReducer can be an alternative to Redux for many projects. * **Don't Do This:** Blindly adopt a state management library without considering whether simpler alternatives would suffice. ### 1.3. Data Fetching and API Interactions * **Standard:** Utilize Astro's "Astro.fetch()" for server-side data fetching. * **Why:** Integrates seamlessly with Astro's build process and offers performance optimizations. "Astro.fetch()" is a wrapper around the standard "fetch()" API, and can be used both at build time, and in API endpoints. * **Do This:** Fetch necessary data during the "getStaticPaths()" or within API routes. * **Don't Do This:** Rely solely on client-side fetching for data that could be fetched server-side. """astro // src/pages/posts.astro export async function getStaticPaths() { const response = await Astro.fetch('https://api.example.com/posts'); const posts = await response.json(); return posts.map(post => ({ params: { id: post.id }, props: { post }, })); } const { post } = Astro.props; """ * **Standard**: For client-side API calls, use "fetch" API with proper error handling and loading states. * **Why:** Provides a standard way to interact with APIs while maintaining a smooth user experience. * **Do This:** Display loading indicators while fetching data and handle potential errors gracefully. * **Example:** """jsx // src/components/UserList.jsx import React, { useState, useEffect } from 'react'; function UserList() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const response = await fetch('/api/users'); // Fetch from an Astro API endpoint. if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data = await response.json(); setUsers(data); } catch (e) { setError(e); } finally { setLoading(false); } }; fetchData(); }, []); if (loading) return <p>Loading users...</p>; if (error) return <p>Error: {error.message}</p>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); } export default UserList; // src/pages/api/users.js export async function get() { try { const users = [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]; // Replace with actual data fetching from a database or external API. return new Response( JSON.stringify(users), { status: 200, headers: { "Content-Type": "application/json" } } ); } catch (error) { console.error(error); return new Response( JSON.stringify({ message: 'Failed to fetch users' }), { status: 500, headers: { "Content-Type": "application/json" } } ); } } """ ## 2. Specific State Management Patterns ### 2.1. Component-Local State with Hooks * **Standard:** Use "useState", "useReducer", "useContext" (from React or Vue) for managing state within individual components. * **Why:** Enables simple reactivity and isolates component logic. * **Do This:** Initialize state with appropriate default values and provide clear update functions. """jsx // src/components/Toggle.jsx import React, { useState } from 'react'; function Toggle() { const [isOn, setIsOn] = useState(false); const toggle = () => { setIsOn(!isOn); }; return ( <div> <button onClick={toggle}> {isOn ? 'Turn Off' : 'Turn On'} </button> <p>Status: {isOn ? 'ON' : 'OFF'}</p> </div> ); } export default Toggle; """ * **Standard:** Avoid deeply nested or complex state objects for local component state. * **Why:** Simplifies updates and reduces the risk of unintended side effects. * **Do This:** Break down complex state into multiple, smaller state variables. ### 2.2. Context API for Shared State * **Standard:** Create custom hooks to consume context values. * **Why:** Improves code readability and avoids repetitive "useContext" calls. It also encapsulates the context usage for cleaner code. * **Do This:** Create a "use[ContextName]" hook that returns the context value or specific properties. """jsx // src/context/AuthContext.jsx import React, { createContext, useState, useContext } from 'react'; const AuthContext = createContext(); export function AuthProvider({ children }) { const [user, setUser] = useState(null); const login = (userData) => { setUser(userData); }; const logout = () => { setUser(null); }; return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider> ); } export function useAuth() { return useContext(AuthContext); } // src/components/Profile.jsx import React from 'react'; import { useAuth } from '../context/AuthContext'; function Profile() { const { user, logout } = useAuth(); if (!user) { return <p>Please log in.</p>; } return ( <div> <p>Welcome, {user.name}!</p> <button onClick={logout}>Logout</button> </div> ); } export default Profile; """ * **Standard:** Limit the scope of Context providers to the components that need access to the shared state. * **Why:** Avoids unnecessary re-renders and maintains performance. Wrap only the components that need the context. * **Don't Do This:** Wrap the entire application in a single Context provider if only a small part of the app interacts with shared state. ### 2.3. External State Management Libraries (Zustand, Jotai, Recoil, Redux) * **Standard:** Use a state management library only when the complexity of the application warrants it. * **Why:** Introduces additional dependencies and complexity. * **Do This:** Carefully consider the trade-offs between simplicity and flexibility. * **Standard:** Follow the recommended patterns and best practices of the chosen library. * **Why:** Ensures maintainability and avoids common pitfalls * **Standard:** Consider using lightweight state management libraries like Zustand or Jotai for simple global state. * **Why:** They offer a simpler API and smaller bundle size compared to Redux. #### 2.3.1 Zustand Example """jsx // store.js (Zustand) import create from 'zustand'; const useStore = create(set => ({ bears: 0, increasePopulation: () => set(state => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }), })); export default useStore; // src/components/BearCounter.jsx import React from 'react'; import useStore from '../store'; function BearCounter() { const bears = useStore(state => state.bears); return <h1>{bears} bears around here!</h1>; } export default BearCounter; // src/components/Controls.jsx import React from 'react'; import useStore from '../store'; function Controls() { 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> </> ); } export default Controls """ ## 3. Anti-Patterns and Common Mistakes * **Anti-Pattern:** Over-reliance on global state for everything. * **Why:** Makes the application harder to reason about, test, and maintain. Can also hurt performance due to excessive re-renders. * **Do This:** Prefer component-local state or context when appropriate. Restrict global state to data that truly needs to be accessed across the entire application. * **Anti-Pattern:** Mutating state directly. * **Why:** Can lead to unexpected side effects and inconsistencies. * **Do This:** Always update state immutably using spread syntax or appropriate update functions provided by the state management library. """jsx // Incorrect: const myState = { items: [1, 2, 3] }; myState.items.push(4); // Direct mutation! // Correct: const myState = { items: [1, 2, 3] }; const newState = { ...myState, items: [...myState.items, 4] }; // Immutable update """ * **Anti-Pattern:** Unnecessary re-renders due to state updates. * **Why:** Hurts performance. * **Do This:** Use "useMemo" and "useCallback" hooks (or equivalent techniques for other UI frameworks) to prevent unnecessary re-renders of components that depend on unchanged state values. Also carefully consider the granularity of your state updates. * **Mistake:** Forgetting to handle loading and error states when fetching data. * **Why:** Degrades user experience. * **Do This:** Display loading indicators and handle potential errors gracefully. * **Mistake** Using client:load or client:visible directives unnecessarily in favor of server-side rendering. * **Why:** Defeats the purpose of Astro by hydrating the components client-side unnecessarily and potentially hurting performance. * **Do This:** Only use these directives when specific client-side interactions or functionality are required and cannot be achieved server-side. ## 4. Security Considerations * **Standard:** Sanitize data received from external sources before storing it in state. * **Why:** Prevents XSS (Cross-Site Scripting) vulnerabilities. * **Do This:** Use appropriate sanitization libraries or techniques recommended by your JavaScript framework (e.g., DOMPurify, "escape-html"). * **Standard:** Avoid storing sensitive information (e.g., API keys, passwords) in client-side state. * **Why:** Exposes sensitive data to potential attackers. * **Do This:** Store sensitive information securely on the server and access it through API endpoints that require authentication. ## 5. Testing State Management * **Standard:** Write unit tests for state update logic, especially when using complex state management libraries. * **Why:** Ensures the correctness and predictability of state transitions. * **Do This:** Use testing frameworks like Jest or Mocha with libraries like React Testing Library to test your state management logic in isolation. * **Standard:** Test component interactions that trigger state updates. * **Why:** Verifies that the UI correctly interacts with and reflects changes in the application state. * **Do This:** Use integration tests or end-to-end tests to simulate user interactions and verify the resulting state changes. ## 6. Tooling and Automation * **Standard:** Utilize linters and code formatters (e.g., ESLint, Prettier) to enforce consistent coding style and best practices related to state management. * **Why:** Automates code quality checks and reduces the risk of errors. * **Do This:** Configure your linter and formatter with rules that enforce immutability, proper error handling, and other state management best practices. * **Standard:** Use TypeScript to add static typing to your state management code. * **Why:** Helps catch type-related errors early in the development process and improves code maintainability. * **Do This:** Define clear type definitions for your state objects and actions.
# Testing Methodologies Standards for Astro This document provides coding standards for testing methodologies in Astro projects, emphasizing unit, integration, and end-to-end (E2E) testing strategies tailored for the Astro framework. It outlines best practices to ensure robust, maintainable, and reliable Astro applications. ## 1. General Testing Principles ### 1.1. Importance of Testing **Do This:** Prioritize writing tests for all components, utilities, and integrations. **Don't Do This:** Neglect writing tests or postpone them until the end of the development cycle. **Why:** Testing ensures code correctness, facilitates easier refactoring, reduces bugs in production, and improves overall code quality. Investing in testing saves time and resources in the long run by preventing costly fixes and downtime. ### 1.2. Test-Driven Development (TDD) **Do This:** Consider adopting TDD where appropriate, writing tests before implementing the actual code. **Don't Do This:** Avoid TDD principles altogether, especially for complex features or critical functionalities. **Why:** TDD helps to clarify requirements, promote cleaner code design, and ensure comprehensive test coverage from the outset. ### 1.3. Testing Pyramid **Do This:** Strive for a well-balanced testing pyramid: * A large base of unit tests. * A substantial layer of integration tests. * A smaller layer of end-to-end tests. **Don't Do This:** Over-rely on E2E tests at the expense of unit and integration tests. **Why:** Unit tests are fast and isolate problems effectively. Integration tests verify interactions between components. E2E tests, while valuable, are slower and more brittle. A balanced pyramid optimizes testing efficiency and coverage. ### 1.4. Code Coverage **Do This:** Aim for high code coverage (e.g., 80% or higher), but focus more on testing critical paths and functionality effectively. **Don't Do This:** Blindly chase 100% code coverage without considering the quality and relevance of the tests. **Why:** Code coverage metrics provide insights into which parts of the code are tested. High coverage reduces the risk of undetected bugs, but meaningful tests are more important than just achieving a specific percentage. ## 2. Unit Testing ### 2.1. Purpose **Do This:** Use unit tests to verify the functionality of individual components, functions, or modules in isolation. **Don't Do This:** Write unit tests that depend on external resources or other components; use mocks or stubs instead. **Why:** Unit tests should be fast, reliable, and focus on the smallest testable units of code. ### 2.2. Tools **Do This:** Utilize modern testing frameworks such as Jest or Vitest. **Don't Do This:** Use outdated or unmaintained testing libraries. **Why:** Modern testing frameworks offer features like mocking, spying, code coverage, and parallel test execution, improving test efficiency and developer experience. Vitest is particularly well-suited for Astro projects as it's built on Vite, like Astro itself. ### 2.3. Example: Testing an Astro Component """astro --- // src/components/MyComponent.astro export interface Props { title: string; } const { title } = Astro.props; --- <h1>{title}</h1> """ """javascript // src/components/MyComponent.test.js (using Vitest) import { render, screen } from '@testing-library/astro'; import MyComponent from './MyComponent.astro'; import { describe, it, expect } from 'vitest'; describe('MyComponent', () => { it('should render the title correctly', async () => { const { container } = await render(MyComponent, { title: 'Hello, Astro!' }); expect(screen.getByText('Hello, Astro!')).toBeInTheDocument(); }); it('should have the correct HTML structure', async () => { const {container} = await render(MyComponent, {title: 'Testing!'}); expect(container.querySelector('h1')).toBeInTheDocument(); expect(container.querySelector('h1')?.textContent).toBe('Testing!'); }); }); """ **Explanation:** * **"@testing-library/astro"**: Used for rendering and querying Astro components. * **"render"**: Renders the component with the provided props. * **"screen"**: Provides methods for querying the rendered output. * **"expect"**: Used for making assertions. * We verify that the component renders the title prop correctly and that the HTML structure is as expected. ### 2.4. Example: Testing a Utility Function """javascript // src/utils/formatDate.js export function formatDate(dateString) { const date = new Date(dateString); const options = { year: 'numeric', month: 'long', day: 'numeric' }; return date.toLocaleDateString(undefined, options); } """ """javascript // src/utils/formatDate.test.js (using Vitest) import { formatDate } from './formatDate'; import { describe, it, expect } from 'vitest'; describe('formatDate', () => { it('should format the date correctly', () => { const dateString = '2024-01-01'; const formattedDate = formatDate(dateString); expect(formattedDate).toBe('January 1, 2024'); }); it('should handle invalid dates gracefully', () => { const dateString = 'invalid-date'; const formattedDate = formatDate(dateString); expect(formattedDate).toBe('Invalid Date'); // Or handle as appropriate }); }); """ **Explanation:** * We test that the utility function formats a valid date string correctly. * We also test how the function handles an invalid date, ensuring it doesn't throw an error and returns an appropriate value. ### 2.5. Mocks and Stubs **Do This:** Use mocks and stubs to isolate the component or function being tested from its dependencies. **Don't Do This:** Directly import and use real dependencies in unit tests. **Why:** Mocks and stubs allow you to control the behavior of dependencies, making tests more predictable and preventing external factors from affecting the test results. """javascript // Example using Jest mocks // src/components/ExternalAPIComponent.js async function fetchData() { const response = await fetch('https://api.example.com/data'); return response.json(); } export default function ExternalAPIComponent() { const data = await fetchData(); return <div>{data.value}</div>; } """ """javascript // src/components/ExternalAPIComponent.test.js import ExternalAPIComponent from './ExternalAPIComponent.js'; import { describe, it, expect, vi } from 'vitest'; import { render, screen, waitFor } from '@testing-library/astro'; global.fetch = vi.fn(() => Promise.resolve({ json: () => Promise.resolve({ value: 'Mocked Data' }), }) ); describe('ExternalAPIComponent', () => { it('should render data from the external API', async () => { const { container } = await render(ExternalAPIComponent); await waitFor(() => { expect(screen.getByText('Mocked Data')).toBeInTheDocument(); }); }); }); """ **Explanation:** * We mock the "global.fetch" function to return a predefined response. * This ensures that the test doesn't rely on the actual external API being available and stable. * The test verifies that the component renders the mocked data correctly. ## 3. Integration Testing ### 3.1. Purpose **Do This:** Use integration tests to verify that different parts of the application work together correctly. **Don't Do This:** Treat integration tests as unit tests or E2E tests. Integration tests should focus on the interactions between components or modules. **Why:** Integration tests catch issues that unit tests might miss, such as incorrect data flow or misconfigured dependencies. ### 3.2. Tools **Do This:** Use the same testing framework as unit tests (e.g., Jest or Vitest) or specialized integration testing tools like Playwright or Cypress. **Don't Do This:** Avoid using tools that are difficult to set up or maintain. **Why:** Using consistent tools simplifies the testing process and reduces the learning curve. ### 3.3. Example: Testing Component Interaction """astro // src/components/ParentComponent.astro import ChildComponent from './ChildComponent.astro'; export interface Props { data: string; } const { data } = Astro.props; --- <div> <ChildComponent message={data} /> </div> """ """astro // src/components/ChildComponent.astro export interface Props { message: string; } const { message } = Astro.props; --- <p>{message}</p> """ """javascript // src/components/ParentComponent.test.js (using Vitest) import { render, screen } from '@testing-library/astro'; import ParentComponent from './ParentComponent.astro'; import { describe, it, expect } from 'vitest'; describe('ParentComponent', () => { it('should render the ChildComponent with the correct message', async () => { const { container } = await render(ParentComponent, { data: 'Hello from Parent!' }); expect(screen.getByText('Hello from Parent!')).toBeInTheDocument(); }); }); """ **Explanation:** * We test that the "ParentComponent" correctly passes the "data" prop to the "ChildComponent". * The test verifies that the "ChildComponent" renders the message as expected. ### 3.4. Mocking External Services in Integration Tests **Do This:** Mock external dependencies in integration tests where possible to avoid relying on live services. **Don't Do This:** Skip mocking essential services, as inconsistent external behavior can make your tests unreliable. """javascript // Example mocking an API call in an integration test // src/services/apiService.js export async function fetchDataFromAPI() { const response = await fetch('/api/data'); return await response.json(); } """ """javascript // src/components/DataComponent.astro import { fetchDataFromAPI } from '../services/apiService'; async function getData() { return await fetchDataFromAPI(); } const data = await getData(); --- <div>{data?.message}</div> """ """javascript // src/components/DataComponent.test.js import { render, screen, waitFor} from '@testing-library/astro'; import DataComponent from './DataComponent.astro'; import { fetchDataFromAPI } from '../services/apiService'; import { describe, it, expect, vi } from 'vitest'; vi.mock('../services/apiService', () => ({ fetchDataFromAPI: vi.fn(() => Promise.resolve({ message: 'Mocked API data' })) })); describe('DataComponent', () => { it('should render data from the API service', async () => { const { container } = await render(DataComponent); await waitFor(() => { expect(screen.getByText('Mocked API data')).toBeInTheDocument(); }); expect(fetchDataFromAPI).toHaveBeenCalled(); }); }); """ **Explanation:** * We mock the "fetchDataFromAPI" function using "vi.mock". * This prevents the integration test from making actual API calls. * We then assert that the mocked method has been called, implying the DataComponent is using it. ## 4. End-to-End (E2E) Testing ### 4.1. Purpose **Do This:** Use E2E tests to verify that the entire application works correctly from the user's perspective. **Don't Do This:** Rely solely on E2E tests; they are slower and more brittle than unit and integration tests. **Why:** E2E tests simulate real user interactions and catch integration issues that other testing types might miss. ### 4.2. Tools **Do This:** Choose E2E testing tools like Playwright or Cypress. **Don't Do This:** Use tools that are difficult to set up, configure, or maintain. **Why:** Playwright and Cypress offer features like automatic waiting, cross-browser testing, and detailed error reporting, improving developer productivity. Playwright is particularly well-suited for Astro due to its modern architecture and excellent TypeScript support. ### 4.3. Example: Testing a Navigation Flow """javascript // playwright.config.js (Playwright configuration file) import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', fullyParallel: true, reporter: 'html', use: { baseURL: 'http://localhost:3000', // Replace with your development server URL trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], webServer: { command: 'npm run dev', // Replace with your development server command port: 3000, reuseExistingServer: !process.env.CI, }, }); """ """javascript // tests/navigation.spec.js (Playwright test file) import { test, expect } from '@playwright/test'; test('navigation to the about page', async ({ page }) => { await page.goto('/'); await page.getByRole('link', { name: 'About' }).click(); await expect(page).toHaveURL('/about'); await expect(page.getByRole('heading', { name: 'About Us' })).toBeVisible(); }); test('check title', async ({ page }) => { await page.goto('https://example.com/'); // Expect a title "to contain" a substring. await expect(page).toHaveTitle(/Example Domain/); }); """ **Explanation:** * We use Playwright to navigate to the homepage ("/") and then click on the "About" link. * We assert that the page URL changes to "/about" and that the "About Us" heading is visible. This verifies the basic navigation flow. ### 4.4. Writing Reliable E2E Tests **Do This:** Write E2E tests that are resilient to minor UI changes and timing issues. **Don't Do This:** Write brittle tests that are prone to failure due to small UI adjustments or network latency. **Why:** Robust E2E tests are crucial for maintaining a stable and reliable application. Avoid using fixed timeouts, and instead favor locating elements by their role/label. ### 4.5. Environment Variables **Do This**: Store environment-specific configuration values such as base URLs in ".env" files and use environment variables to configure your tests. **Don't Do This**: Hardcode URLs or API keys directly in your tests. **Why**: Using environment variables ensures that your tests can be run in different environments (e.g., development, staging, production) without requiring code changes. ## 5. Accessibility Testing. ### 5.1 Importance of Accessibility **Do This:** Implement accessibility checks in your tests to ensure your application is usable by everyone. **Don't Do This:** Neglect accessibility testing, as it can exclude users and lead to legal issues. **Why:** Accessible applications provide a better user experience for everyone, including users with disabilities. ### 5.2 Tools **Do This**: Use accessibility testing tools like axe-core along with testing frameworks. **Don't Do This**: Depend solely on manual checks without automated testing. **Why**: Automated accessibility testing can catch common accessibility issues early in the development process. ### 5.3 Example Accessibility test """javascript // tests/e2e/accessibility.spec.js import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; test.describe('Accessibility', () => { test('homepage should not have any automatically detectable accessibility issues', async ({ page }) => { await page.goto('/'); const axeBuilder = new AxeBuilder({ page }); const accessibilityScanResults = await axeBuilder.analyze(); expect(accessibilityScanResults.violations).toEqual([]); }); }); """ ## 6. Performance Testing ### 6.1 Importance of Performance Testing **Do This:** Measure the performance of your Astro application using appropriate tools and techniques. **Don't Do This:** Ignore performance testing, as slow applications can lead to poor user experience and loss of business. **Why:** Performance testing helps identify bottlenecks and areas for optimization in your application. ### 6.2 Tools **Do This:** Use performance testing tools like Lighthouse, WebPageTest, or browser developer tools. **Don't Do This:** Rely solely on manual observations without quantitative metrics. **Why:** These tools provide detailed insights into various performance metrics, such as load time, rendering time, and resource utilization. ## 7. Common Anti-Patterns to Avoid ### 7.1. Flaky Tests **Don't Do This:** Accept flaky tests (tests that pass sometimes and fail sometimes). **Do This:** Investigate and fix flaky tests; they undermine confidence in the test suite. **Why:** Flaky tests often indicate underlying issues in the code or test setup. ### 7.2. Over-mocking **Don't Do This:** Mock everything in sight. **Do This:** Mock only the dependencies that are necessary to isolate the unit under test. **Why:** Over-mocking can lead to tests that don't accurately reflect the behavior of the real system. ### 7.3. Ignoring Test Coverage **Don't Do This:** Ignore code coverage metrics. **Do This:** Use code coverage reports to identify areas of the code that are not adequately tested. **Why:** Code coverage helps to ensure that all parts of the code are covered by tests. ### 7.4. Hardcoded Values **Don't Do This:** Hardcode values in tests. **Do This:** Use test data generators or fixtures to create realistic and maintainable test data. **Why:** Hardcoded values make tests brittle and difficult to maintain. By adhering to these coding standards, you can ensure that your Astro projects are well-tested, maintainable, and reliable. Remember to continuously review and update these standards as the Astro framework and testing tools evolve.