# Security Best Practices Standards for Qwik
This document outlines security best practices for Qwik applications. It aims to guide developers in building secure, robust, and maintainable Qwik applications by focusing on preventing common vulnerabilities and adopting secure coding patterns specific to the Qwik framework.
## 1. General Security Principles
Before diving into Qwik-specific implementations, it's essential to understand fundamental security principles. Applying these principles throughout the development lifecycle minimizes the attack surface and enhances overall application security.
* **Principle of Least Privilege:** Grant the minimum necessary permissions to users and components. Only allow access to the specific resources and operations required to perform a task.
* **Defense in Depth:** Implement multiple layers of security controls. If one layer fails, another layer can prevent an attack from succeeding.
* **Input Validation:** Validate all user input to prevent injection attacks and ensure data integrity.
* **Output Encoding:** Encode output data to prevent cross-site scripting (XSS) attacks.
* **Regular Security Audits:** Conduct regular security assessments and penetration testing to identify and address vulnerabilities.
* **Keep Dependencies Updated:** Regularly update dependencies to patch security vulnerabilities.
## 2. Qwik-Specific Security Considerations
Qwik's unique architecture brings specific security implications that developers must address. Considerations include handling of server-side and client-side rendering, managing state, and securing API endpoints.
### 2.1. Server-Side Rendering (SSR) Security
Qwik's SSR capability introduces potential security vulnerabilities if handled improperly. It is crucial to sanitize data passed from the server to the client to prevent XSS attacks.
* **Do This:**
* Use Qwik's built-in sanitization mechanisms, along with proper output encoding for all dynamic content rendered on the server.
* Implement Content Security Policy (CSP) to restrict the sources from which the browser can load resources.
* **Don't Do This:**
* Directly inject unsanitized user input into HTML markup during SSR.
**Example:**
"""typescript
// Safe SSR rendering with Qwik
import { component$, useRenderEffect } from '@builder.io/qwik';
import { sanitize } from 'dompurify'; // or similar sanitization library
import { getRequestEvent } from '@builder.io/qwik-city';
export const MyComponent = component$(() => {
const event = getRequestEvent();
const userInput = event?.params?.userInput || ''; // safe default
const sanitizedInput = sanitize(userInput);
useRenderEffect(() => {
// Log the sanitized input (for demonstration)
console.log('Sanitized input:', sanitizedInput);
});
return (
Hello, {sanitizedInput}!
);
});
"""
**Why this matters:** The "sanitize" function removes potentially malicious HTML from the "userInput" preventing XSS vulnerabilities when the component is rendered on the server and then hydrated on the client.
### 2.2. Client-Side Security
Qwik applications are JavaScript-heavy on the client-side as well. Protect the client-side code from manipulation and unauthorized access.
* **Do This:**
* Use strict mode (""use strict";") in your JavaScript files.
* Minimize the amount of sensitive data stored in the client-side code.
* Use environment variables for configuration, and avoid exposing sensitive information directly in the code.
* **Don't Do This:**
* Store API keys or other sensitive credentials in the client-side code.
* Trust data received from the client without validation on the server.
**Code Example:**
"""typescript
// Using environment variables securely
import { getEnv } from '@builder.io/qwik-city';
export const useApi = () => {
const API_ENDPOINT = getEnv('PUBLIC_API_ENDPOINT');
async function fetchData(path: string) {
if (!API_ENDPOINT) {
throw new Error('API Endpoint not configured'); // Handle missing env variable correctly
}
const response = await fetch("${API_ENDPOINT}/${path}");
if (!response.ok) {
throw new Error("HTTP error! status: ${response.status}");
}
return await response.json();
}
return { fetchData };
};
"""
**Why this matters:** Using "getEnv" ensures that the API endpoint is retrieved from environment variables, which are not exposed in the client-side code. It adds a check to ensure the environment variable is defined. Also includes basic error handling.
### 2.3. API Endpoint Security
Qwik applications often interact with APIs. Securing these endpoints is crucial to prevent unauthorized access and data breaches.
* **Do This:**
* Implement authentication and authorization mechanisms to control access to API endpoints.
* Use HTTPS to encrypt data in transit.
* Implement rate limiting to prevent denial-of-service (DoS) attacks.
* Validate and sanitize all request data on the server-side.
* **Don't Do This:**
* Expose API endpoints without proper authentication or authorization.
* Trust data received from the client without validation.
* Store sensitive data in plain text in the database.
**Example:**
"""typescript
// Secure API endpoint using Qwik City
import { RequestEvent, RequestHandler } from '@builder.io/qwik-city';
import { verifyAuthToken } from './auth-utils'; // Custom auth utility
export const onGet: RequestHandler = async (event: RequestEvent) => {
const authHeader = event.request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
event.fail(401, 'Unauthorized');
return;
}
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
try {
const user = await verifyAuthToken(token);
if (!user) {
event.fail(401, 'Unauthorized');
return;
}
// Fetch data based on user role or permissions if desired
const data = { message: 'Secure data', user };
event.json(200, data);
} catch (error) {
console.error('Authentication error:', error);
event.fail(401, 'Unauthorized');
}
};
"""
**Why this matters:** This example uses a "verifyAuthToken" function to authenticate the user based on a bearer token from Authorization header. The endpoint returns a 401 error if the user is not authenticated. The actual implementation for "verifyAuthToken" would involve JWT verification, database lookup, or other authentication mechanisms.
### 2.4. Data Validation and Sanitization
Data validation and sanitization are critical for preventing injection attacks and ensuring data integrity.
* **Do This:**
* Validate all user input on both the client-side and server-side.
* Use a validation library to define and enforce data validation rules.
* Sanitize data to remove potentially malicious characters or code.
* **Don't Do This:**
* Trust data received from the client without validation.
* Store unsanitized data in the database.
**Example:**
"""typescript
// Data validation using a library like zod
import { z } from 'zod';
const UserSchema = z.object({
username: z.string().min(3).max(20),
email: z.string().email(),
password: z.string().min(8),
});
export const validateUserData = (data: any) => {
try {
return UserSchema.parse(data); // Returns the validated data
} catch (error: any) {
console.error("Validation error:", error.errors); // Detailed error reporting
return null; // Or throw the error if you prefer to handle it elsewhere
}
};
// Usage in a Qwik form
import { component$ } from '@builder.io/qwik';
import { useForm } from '@builder.io/qwik-city'; // consider using this or a similar helper for form handling
export const UserFormComponent = component$(() => {
const form = useForm({
onSubmit: async (values) => {
const validatedData = validateUserData(values);
if (validatedData) {
// ... proceed with validated data (e.g., API call)
console.log("Validated Data:", validatedData);
} else {
// Handle validation errors (e.g., display to user)
console.error("Form validation failed");
}
},
});
return (
{/* Form inputs */}
Submit
);
});
"""
**Why this matters:** This example uses Zod schema to define the expected structure and types of user data. The parsing method either returns the validated data or returns an error object. Ensures data conforms to the defined schema before further processing.
### 2.5. Dependency Management and Security
Managing dependencies is crucial for staying secure. Vulnerable dependencies are a common attack vector.
* **Do This:**
* Use a dependency management tool (e.g., npm, yarn, pnpm).
* Regularly update dependencies to patch security vulnerabilities.
* Use a tool to scan dependencies for known vulnerabilities (e.g., "npm audit", "yarn audit").
* Consider using tools like "snyk" or "whitesource bolt" for advanced vulnerability scanning and remediation.
* **Don't Do This:**
* Use outdated dependencies with known vulnerabilities.
* Ignore security warnings from dependency management tools.
**Example:**
1. **Regularly run audit commands:**
"""console
npm audit
# or
yarn audit
# or
pnpm audit
"""
2. **Update vulnerable dependencies:**
"""console
npm update
# or
yarn upgrade
# or
pnpm update
"""
3. **Use "npm audit fix" for automatic fixing (use cautiously):**
"""console
npm audit fix
"""
**Why this matters:** Regularly auditing and updating dependencies helps to ensure that the application is not vulnerable to known security exploits.
### 2.6. Cross-Site Scripting (XSS) Prevention
XSS attacks occur when malicious scripts are injected into web pages viewed by other users. Qwik follows the same principles as other frameworks, but the lazy loading and resumability aspects require special attention.
* **Do This:**
* Sanitize all user input before rendering it in the browser.
* Use Content Security Policy (CSP) to restrict the sources from which the browser can load resources.
* Escape HTML entities when rendering user-provided data.
* **Don't Do This:**
* Directly inject user input into HTML markup without sanitization.
* Disable CSP without a good reason.
**Example:**
See section 2.1 for data sanitization code. Also, configure your HTTP server to send the correct CSP headers.
"""
Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://example.com;
"""
**Why this matters:** Proper sanitization and CSP headers prevent malicious scripts from being executed in the browser, protecting users from XSS attacks.
### 2.7. Cross-Site Request Forgery (CSRF) Prevention
CSRF attacks occur when an attacker tricks a user into performing an action on a web application without their knowledge.
* **Do This:**
* Use CSRF tokens to protect sensitive actions.
* Use the "SameSite" attribute for cookies to prevent cross-site request forgery.
* Implement double-submit cookies to verify the origin of requests.
* **Don't Do This:**
* Rely solely on cookies for authentication.
* Allow GET requests to perform sensitive actions.
**Example:**
"""typescript
// CSRF Token Generation (Server-Side)
import { generate as generateCSRF } from 'nanoid'; // nanoid is good for CSRF
export async function generateCsrfToken(session: any): Promise {
const csrfToken = generateCSRF();
session.csrfToken = csrfToken; // Store in user session, database, etc.
return csrfToken;
}
// CSRF Token Verification (Server-Side)
export async function verifyCsrfToken(token: string, session: any): Promise {
if (!session.csrfToken || session.csrfToken !== token) {
return false;
}
delete session.csrfToken; // Use once, delete to mitigate replay attacks
return true;
}
// Usage in Qwik (Server and Client, simplified)
import { RequestEvent, RequestHandler } from '@builder.io/qwik-city';
import { component$, useClientEffect$, useState } from '@builder.io/qwik';
//... (Assuming session management is set up)
export const MyFormComponent = component$(() => {
const [csrfToken, setCsrfToken] = useState('');
useClientEffect$(async ({ track }) => {
// Get CSRF token
const generateCSRF = async (): Promise => {
const response = await fetch("/api/csrf",{method:"GET"});
const data = await response.json();
return data.csrfToken
}
const token = await generateCSRF()
setCsrfToken(token)
});
return (
{/*input items...*/}
Submit
);
});
// API endpoint (Server-Side)
export const onSubmit: RequestHandler = async (event: RequestEvent) => {
const formData = await event.request.formData();
const csrfToken = formData.get('csrfToken');
//... retrieve session info (database, etc)
if (! await verifyCsrfToken(csrfToken, {csrfToken: "DUMMY"})) { //replace {csrfToken: "DUMMY"} with your session object
event.fail(403, 'CSRF token validation failed');
return;
}
// Process the form data...
event.json(200, { message: 'Form submitted successfully!' });
};
export const onGet: RequestHandler = async (event: RequestEvent) => {
//Assuming you have session info set with requestEvent
const sessionInfo:any = {} //replace with your actual session data
const csrfToken = await generateCsrfToken(sessionInfo);
event.json(200, { csrfToken: csrfToken });
};
"""
**Why this matters:** CSRF tokens ensure that only legitimate users can perform sensitive actions on the web application. Make sure 'DUMMY' session info is replaces, and that you implement proper server session and storage of the csrf tokens.
### 2.8. Clickjacking Prevention
Clickjacking attacks occur when an attacker tricks a user into clicking on something different from what the user perceives.
* **Do This:**
* Use the "X-Frame-Options" header to prevent the application from being framed.
* Use Content Security Policy (CSP) to restrict the sources from which the browser can load resources.
* **Don't Do This:**
* Allow the application to be framed by any origin.
**Example:**
Configure your HTTP server to send the "X-Frame-Options" header.
"""
X-Frame-Options: SAMEORIGIN
"""
**Why this matters:** The "X-Frame-Options" header prevents the application from being framed by malicious websites, protecting users from clickjacking attacks. You can also use the more powerful "Content-Security-Policy" to achieve similar results combined with other security benefits.
"""
Content-Security-Policy: frame-ancestors 'self';
"""
### 2.9. Security Headers
Setting appropriate security headers in the HTTP response is essential for enhancing the security of your Qwik application.
* **Do This:**
* Set the "Content-Security-Policy" header to restrict the sources from which the browser can load resources.
* Set the "X-Frame-Options" header to prevent clickjacking attacks.
* Set the "X-Content-Type-Options" header to prevent MIME sniffing attacks.
* Set the "Strict-Transport-Security" header to enforce HTTPS connections.
* Set the "Referrer-Policy" header to control how much referrer information is sent with requests.
**Example:**
Configure your HTTP server to send the following headers:
"""
Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://example.com;
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Referrer-Policy: strict-origin-when-cross-origin
"""
**Why this matters:** These headers provide various security benefits, protecting users from XSS, clickjacking, MIME sniffing, and other attacks.
## 3. Qwik-Specific Tools and Techniques
Leverage Qwik's features and ecosystem to enhance security.
* **Qwik City Security Middleware:** Utilize middleware to handle common security tasks like CSRF protection, header setting, and authentication for Qwik City applications.
* **Server Functions:** When possible, server-side tasks should be in Server Functions.
* **Qwik's Resumability:** Be aware that any state serialized for resumability could be exposed. Do not serialize sensitive data unnecessarily.
## 4. Monitoring and Logging
Effective monitoring and logging are crucial for detecting and responding to security incidents.
* **Do This:**
* Implement centralized logging to collect and analyze security-related events.
* Monitor application logs for suspicious activity.
* Set up alerts for critical security events.
* **Don't Do This:**
* Store sensitive data in logs.
* Ignore security warnings or errors in logs.
**Example:**
"""typescript
// Basic logging example
import { getRequestEvent } from '@builder.io/qwik-city';
export const logEvent = (message: string, level: 'info' | 'warn' | 'error') => {
const event = getRequestEvent(); // May be null in certain contexts
const logData = {
timestamp: new Date().toISOString(),
level: level,
message: message,
url: event?.request.url || 'N/A', // optional
ip: event?.clientAddress || 'N/A', //optional (use with caution)
};
console.log(JSON.stringify(logData)); // Replace with a more robust logging solution
// Consider using a dedicated logging library (e.g., winston, morgan)
// that supports different transports (e.g., file, database, cloud services)
};
// Usage
logEvent('User login attempt failed.', 'warn');
"""
**Why this matters:** Centralized logging and monitoring enable prompt detection and response to security incidents, minimizing the impact of attacks. Avoid logging sensitive information, especially PII.
## 5. Conclusion
These coding standards provide a foundation for building secure Qwik applications. Stay up-to-date with the latest security best practices and guidelines, regularly review and update your application's security measures, and foster a security-conscious culture within your development team. By prioritizing security at every stage of the development lifecycle, you can create robust and trustworthy Qwik applications. This document serves as a starting point, continuously adapt and expand it based on your specific application requirements and evolving security landscape.
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'
# Code Style and Conventions Standards for Qwik This document outlines the coding style and conventions standards for Qwik projects, aiming to ensure consistency, readability, and maintainability across the codebase. These standards are designed to be used in conjunction with other rules in this series (e.g., architecture, security), providing a comprehensive guide for Qwik development. These conventions help create code that is easy to understand, debug, and extend, while also leveraging Qwik's unique features for optimal performance. ## 1. General Formatting Consistent formatting is crucial for code readability. Qwik projects should adhere to the following general formatting rules: ### 1.1. Whitespace * **Do This:** * Use 2 spaces for indentation. This helps make your code more readable on smaller screens and promotes a consistent visual structure. """typescript // Good: 2 spaces for indentation export const MyComponent = component$(() => { return ( <div> <p>Hello, Qwik!</p> </div> ); }); """ * **Don't Do This:** * Avoid using tabs for indentation. This can lead to inconsistent formatting across different editors and environments. """typescript // Bad: Tabs for indentation export const MyComponent = component$(() => { return ( <div> <p>Hello, Qwik!</p> </div> ); }); """ * **Why:** Consistent indentation improves code readability and reduces visual clutter. Using spaces ensures consistency across different development environments. ### 1.2. Line Length * **Do This:** * Limit lines to a maximum of 120 characters. Longer lines can be difficult to read and manage. """typescript // Good: Lines under 120 characters export const LongComponentName = component$(() => { return ( <div style={{ width: '100%', backgroundColor: 'lightblue', padding: '10px' }}> <p>This is a component with a long name and some styling.</p> </div> ); }); """ * **Don't Do This:** * Avoid excessively long lines that wrap around the editor. """typescript // Bad: Lines exceeding 120 characters export const LongComponentName = component$(() => { return ( <div style={{ width: '100%', backgroundColor: 'lightblue', padding: '10px', border: '1px solid gray', margin: '5px' }}> <p>This is a component with a long name and some styling. It should be avoided to exceed line limits.</p> </div> ); }); """ * **Why:** Limiting line length improves readability, especially on smaller screens or when comparing different versions of code. ### 1.3. Blank Lines * **Do This:** * Use blank lines to separate logical sections of code. This visually groups related code and improves readability. """typescript // Good: Blank lines for separation export const MyComponent = component$(() => { // Data fetching logic const data = useStore({ name: 'Qwik' }); // Rendering the component return ( <div> <p>Hello, {data.name}!</p> </div> ); }); """ * **Don't Do This:** * Avoid excessive blank lines that create large gaps in the code. """typescript // Bad: Excessive blank lines export const MyComponent = component$(() => { // Data fetching logic const data = useStore({ name: 'Qwik' }); // Rendering the component return ( <div> <p>Hello, {data.name}!</p> </div> ); }); """ * **Why:** Judicious use of blank lines helps to visually organize code into logical blocks, making it easier to understand the structure and flow. ### 1.4. Trailing Commas * **Do This:** * Include trailing commas in multi-line object literals, array literals, and function parameter lists. This simplifies adding, removing, or reordering items. """typescript // Good: Trailing commas const config = { name: 'My App', version: '1.0.0', author: 'John Doe', // Trailing comma }; """ * **Don't Do This:** * Omit trailing commas, as this can lead to unnecessary diffs in version control systems """typescript // Bad: Missing trailing comma const config = { name: 'My App', version: '1.0.0', author: 'John Doe' }; """ * **Why:** Trailing commas make version control diffs cleaner when adding, removing, or reordering items in lists and objects. ### 1.5. Ending Files * **Do This:** * End every file on a newline. ## 2. Naming Conventions Clear and consistent naming conventions are essential for understanding the purpose of different elements in the codebase. ### 2.1. Variables * **Do This:** * Use camelCase for variable names. This convention is widely used in JavaScript and makes variables easily identifiable. """typescript // Good: camelCase variable names const userName = 'John Doe'; const itemCount = 10; """ * **Don't Do This:** * Avoid snake_case or PascalCase for variable names. """typescript // Bad: snake_case variable names const user_name = 'John Doe'; // Bad: PascalCase variable names const UserName = 'John Doe'; """ * **Why:** camelCase is the standard convention in JavaScript and enhances code consistency. ### 2.2. Constants * **Do This:** * Use UPPER_SNAKE_CASE for constants. This clearly distinguishes constants from variables. """typescript // Good: UPPER_SNAKE_CASE constants const API_URL = 'https://example.com/api'; const MAX_ITEMS = 20; """ * **Don't Do This:** * Avoid camelCase or PascalCase for constants. """typescript // Bad: camelCase constants const apiUrl = 'https://example.com/api'; // Bad: PascalCase constants const ApiUrl = 'https://example.com/api'; """ * **Why:** Using UPPER_SNAKE_CASE for constants makes it immediately clear that these values should not be modified. ### 2.3. Functions * **Do This:** * Use camelCase for function names. This maintains consistency with variable naming. """typescript // Good: camelCase function names function calculateTotal(price: number, quantity: number): number { return price * quantity; } const handleClick = () => { console.log('Button clicked!'); }; """ * **Don't Do This:** * Avoid PascalCase or snake_case for function names. """typescript // Bad: PascalCase function names function CalculateTotal(price: number, quantity: number): number { return price * quantity; } // Bad: snake_case function names function calculate_total(price: number, quantity: number): number { return price * quantity; } """ * **Why:** camelCase is the standard convention in JavaScript and enhances code consistency. ### 2.4. Components * **Do This:** * Use PascalCase for component names. This distinguishes components from regular functions or variables. """typescript // Good: PascalCase component names export const MyComponent = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid camelCase or snake_case for component names. """typescript // Bad: camelCase component names export const myComponent = component$(() => { return <p>Hello, Qwik!</p>; }); // Bad: snake_case component names export const my_component = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Why:** PascalCase is the standard convention for React components (and adopted by Qwik), ensuring consistent naming across the codebase. ### 2.5. Files and Directories * **Do This:** * Use kebab-case for file and directory names related to components or routes. Consistent naming makes the project structure more predictable. """ // Good: kebab-case file and directory names src/ └── components/ ├── my-component/ │ ├── my-component.tsx │ └── my-component.css └── other-component/ ├── other-component.tsx └── other-component.css """ * **Don't Do This:** * Avoid camelCase or snake_case for file and directory names. """ // Bad: camelCase file and directory names src/ └── components/ ├── myComponent/ │ ├── myComponent.tsx │ └── myComponent.css └── otherComponent/ ├── otherComponent.tsx └── otherComponent.css """ * **Why:** kebab-case is a common convention for file and directory names in web development, contributing to a more consistent project structure. ## 3. Qwik-Specific Conventions Qwik introduces specific concepts like "component$", "useStore", and "useTask$". Following specific conventions for these elements is essential. ### 3.1. "component$" * **Do This:** * Use "component$" for creating components that leverage Qwik's resumability features. Always lazy-load components using "component$". """typescript // Good: Using component$ for lazy-loaded components import { component$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid using regular function declarations for creating components that should be resumable. Also, avoid defining components inline with other logic. """typescript // Bad: Not using component$ export function OldComponent() { return <p>Hello, Old Qwik!</p>; } """ * **Why:** "component$" ensures that your components are lazy-loaded and resumable, improving performance and initial load time. ### 3.2. "useStore" * **Do This:** * Use "useStore" for managing component state that needs to be persisted across resumability boundaries. """typescript // Good: Using useStore for component state import { component$, useStore } from '@builder.io/qwik'; export const MyComponent = component$(() => { const store = useStore({ name: 'Qwik', count: 0, }); return ( <div> <p>Hello, {store.name}! Count: {store.count}</p> </div> ); }); """ * **Don't Do This:** * Avoid using regular "useState" from React, as it might not be compatible with Qwik's resumability features. """typescript // Bad: Using useState (React) import { component$ } from '@builder.io/qwik'; import { useState } from 'react'; export const MyComponent = component$(() => { const [name, setName] = useState('Qwik'); // This is wrong in Qwik. return ( <div> <p>Hello, {name}!</p> </div> ); }); """ * **Why:** "useStore" is designed to work with Qwik's resumability, ensuring that your component state is correctly persisted and restored. It provides efficient serialization and deserialization for optimal performance. ### 3.3. "useTask$" * **Do This:** * Use "useTask$" to perform side effects that need to run after rendering, and to re-run them whenever specific dependencies change. """typescript // Good: Using useTask$ for side effects import { component$, useTask$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { useTask$(() => { console.log('Component rendered!'); }); return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid using "useClientEffect$" for tasks better suited for execution after server-side rendering or during rehydration on the client, as "useClientEffect$" runs only on the client. Direct DOM manipulation within components (except within "useTask$") """typescript // Bad: Using useClientEffect$ when not needed import { component$, useClientEffect$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { useClientEffect$(() => { console.log('Component rendered on client!'); }); return <p>Hello, Qwik!</p>; }); """ * **Why:** "useTask$" ensures that side effects are properly handled during server-side rendering and client-side rehydration, maintaining consistency and avoiding unexpected behavior. ### 3.4 "useSignal" * **Do This:** * Use "useSignal" when you need reactivity at a more granular level, rather than for entire component state. Signals can be used for tracking very specific values that trigger updates. """typescript import { component$, useSignal } from '@builder.io/qwik'; export const MyComponent = component$(() => { const count = useSignal(0); return ( <div> <p>Count: {count.value}</p> <button onClick$={() => count.value++}>Increment</button> </div> ); }); """ * **Don't Do This:** * Overuse signals for managing large chunks of component state. For larger states, "useStore" is typically more appropriate. * **Why:** "useSignal" provides fine-grained reactivity with minimal overhead, perfect for specific values. However, for complex state management, "useStore" is better suited. ### 3.5. Props * **Do This:** * Explicitly define prop types using interfaces. This provides type safety and enhances code maintainability. """typescript import { component$ } from '@builder.io/qwik'; interface MyComponentProps { name: string; age: number; } export const MyComponent = component$((props: MyComponentProps) => { return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Don't Do This:** * Avoid using "any" for prop types, as this removes type safety and increases the risk of errors. """typescript import { component$ } from '@builder.io/qwik'; export const MyComponent = component$((props: any) => { // Avoid 'any' return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Why:** Explicit prop types improve code clarity and prevent type-related errors, leading to more robust and maintainable components. ## 4. Stylistic Consistency Maintaining stylistic consistency across the codebase improves readability and reduces cognitive load for developers. ### 4.1. Quotes * **Do This:** * Use single quotes (') for JSX attributes and double quotes (") for strings, but choose a single one and adhere to it for the entire project. This is a common convention in JavaScript and HTML. """typescript // Good: Single quotes for attributes, double quotes for strings export const MyComponent = component$(() => { return ( <div className="my-class"> {/*className uses double quotes (but single quotes are also fine if all is consistent)*/} <p>Hello, Qwik!</p> </div> ); }); """ * **Don't Do This:** * Inconsistently use single and double quotes in JSX attributes or strings, leading to visual clutter and reduced readability. """typescript // Bad: Inconsistent quote usage export const MyComponent = component$(() => { return ( <div className='my-class'> {/*className uses single quotes*/} <p>Hello, Qwik!</p> </div> ); }); """ * **Why:** Consistent quote usage improves code readability and reduces visual noise. ### 4.2. Arrow Functions * **Do This:** * Prefer arrow functions for concise function expressions, especially in functional components and callbacks. """typescript // Good: Using arrow functions const handleClick = () => { console.log('Button clicked!'); }; export const MyComponent = component$(() => { return <button onClick$={handleClick}>Click me</button>; }); """ * **Don't Do This:** * Use traditional function declarations when arrow functions would be more concise and readable. """typescript // Bad: Using traditional function declaration function handleClick() { console.log('Button clicked!'); } export const MyComponent = component$(() => { return <button onClick$={handleClick}>Click me</button>; }); """ * **Why:** Arrow functions are more concise and have lexical "this" binding, making them ideal for functional components and callbacks. ### 4.3. Object and Array Literals * **Do This:** * Use concise syntax for object and array literals. This improves readability and reduces boilerplate code. """typescript // Good: Concise object and array literals const user = { name: 'John', age: 30 }; const numbers = [1, 2, 3, 4, 5]; """ * **Don't Do This:** * Use verbose or outdated syntax for object and array literals. """typescript // Bad: Verbose object and array literals const user = new Object(); user.name = 'John'; user.age = 30; const numbers = new Array(); numbers[0] = 1; numbers[1] = 2; """ * **Why:** Concise syntax for object and array literals makes the code more readable and maintainable. ### 4.4. Ternary Operators * **Do This:** * Use ternary operators for simple conditional expressions. This makes the code more concise and readable. """typescript // Good: Using ternary operator const isLoggedIn = true; const message = isLoggedIn ? 'Welcome!' : 'Please log in.'; """ * **Don't Do This:** * Use ternary operators for complex conditional logic, as this can reduce readability. """typescript // Bad: Complex ternary operator const isLoggedIn = true; const userRole = 'admin'; const message = isLoggedIn ? userRole === 'admin' ? 'Welcome, Admin!' : 'Welcome, User!' : 'Please log in.'; """ * **Why:** Ternary operators provide a concise way to express simple conditional logic. For more complex logic, use "if" statements for better readability. ## 5. Error Handling Effective error handling is crucial for building robust and maintainable applications. ### 5.1. Try-Catch Blocks * **Do This:** * Use "try-catch" blocks to handle potential errors that might occur during runtime, especially when dealing with asynchronous operations or external APIs. """typescript // Good: Using try-catch block async function fetchData() { try { const response = await fetch('https://example.com/api/data'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); return null; } } """ * **Don't Do This:** * Ignore potential errors without handling them, as this can lead to unexpected behavior and difficult debugging. """typescript // Bad: Ignoring potential errors async function fetchData() { const response = await fetch('https://example.com/api/data'); const data = await response.json(); // If fetch call fails, exception will halt execution return data; } """ * **Why:** "try-catch" blocks allow you to gracefully handle errors, preventing the application from crashing and providing informative error messages. ### 5.2. Error Boundaries * **Do This:** * Implement error boundaries in your Qwik components to catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the component. """typescript // Good: Error Boundary Component import { component$, ErrorBoundary } from '@builder.io/qwik'; interface Props { children: JSX.Element; } export const ErrorBoundaryComponent = component$((props: Props) => { return ( <ErrorBoundary> {props.children} <template q:fallback> <div> <h2>Something went wrong.</h2> <p>Please try again later.</p> </div> </template> </ErrorBoundary> ); }); """ * **Don't Do This:** * Rely solely on global error handlers, as they might not provide sufficient context and can make it difficult to isolate the source of the error. Avoid letting errors crash the entire application. * **Why:** Error boundaries ensure that errors in one part of the application do not affect other parts, improving the overall user experience and maintainability. ### 5.3 Handling Promises * **Do This:** * Always handle promise rejections, either with ".catch()" or by using "async/await" within a "try...catch" block. Unhandled promise rejections can lead to unhandled exceptions and unexpected behavior. """typescript // Good: Handling promise rejections with .catch() fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error fetching data:', error)); // Good: Handling promise rejections with async/await and try...catch async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data); } catch (error) { console.error('Error fetching data:', error); } } """ * **Don't Do This:** * Ignore promise rejections. This can mask errors and lead to unexpected behavior in your application. """typescript // Bad: Ignoring promise rejections fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)); // What if the fetch fails? """ * **Why:** Proper promise rejection handling ensures that errors are caught and handled, leading to a more stable and reliable application. ## 6. Comments and Documentation Clear and concise comments are essential for understanding the purpose and functionality of the code. ### 6.1. Code Comments * **Do This:** * Add comments to explain complex or non-obvious logic. Comments should provide context, explain the purpose of the code, and guide the reader through the implementation. """typescript // Good: Code comments /** * Calculates the discount amount based on the user's membership level. * @param price The original price of the item. * @param membershipLevel The user's membership level (e.g., 'Basic', 'Premium', 'Gold'). * @returns The discount amount. */ function calculateDiscount(price: number, membershipLevel: string): number { let discountPercentage = 0; switch (membershipLevel) { case 'Premium': discountPercentage = 0.1; // 10% discount for Premium members break; case 'Gold': discountPercentage = 0.2; // 20% discount for Gold members break; default: discountPercentage = 0; // No discount for other membership levels } return price * discountPercentage; } """ * **Don't Do This:** * Add redundant comments that simply restate what the code already does. Avoid also using comments to document obvious code. """typescript // Bad: Redundant comments const x = 5; // Assign 5 to x => This isn't descriptive, this is only restating! """ * **Why:** Comments should provide valuable information that is not immediately apparent from the code itself. They should explain the *why* and not just the *what*. ### 6.2. JSDoc Comments * **Do This:** * Use JSDoc-style comments at the beginning of functions and components to describe parameters, return values, and any side effects. This allows IDEs and documentation generators to provide better assistance and documentation. """typescript /** * Fetches user data from the API. * @param id The ID of the user to fetch. * @returns A promise that resolves with the user data, or rejects with an error. */ async function fetchUserData(id:number): Promise<any> { //... } """ ### 6.3. Component Documentation * **Do This:** * Include a comment block at the top of each component file that describes the purpose of the component, its props, and any other relevant information. """typescript // Good: Component documentation /** * A component that displays a user's profile. * * @param props The props for the component. * @param props.name The name of the user. * @param props.age The age of the user. */ import { component$ } from '@builder.io/qwik'; interface MyComponentProps { name: string; age: number; } export const MyComponent = component$((props: MyComponentProps) => { return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Don't Do This:** * Omit component documentation, as this can make it difficult for other developers to understand the purpose and usage of the component. * **Why:** Clear component documentation facilitates code reuse and collaboration, helping other developers quickly understand how to use the component and what to expect from it. ## 7. Imports and Exports Properly managing imports and exports is critical for maintaining a clear and modular codebase. ### 7.1. Absolute Imports * **Do This:** * Favor absolute imports over relative imports, especially for modules in deeply nested directories. Absolute imports make it easier to refactor code and move files without breaking import paths. """typescript // Good: Absolute import import { MyComponent } from 'src/components/my-component'; """ * **Don't Do This:** * Use relative imports that rely on the file's location within the directory structure, as this can make the code more brittle and difficult to refactor. """typescript // Bad: Relative import import { MyComponent } from '../../components/my-component'; """ * **Why:** Absolute imports provide a more stable and predictable way to reference modules, reducing the risk of broken imports during refactoring. They also improve code readability by making it clear where modules are located within the project structure. ### 7.2. Grouping Imports * **Do This:** * Group imports from the same module together and separate them from imports from other modules. This improves code readability and makes it easier to identify dependencies. """typescript // Good: Grouped imports // Qwik imports import { component$, useStore } from '@builder.io/qwik'; // Component imports import { Button } from './button'; import { Input } from './input'; // Utility imports import { formatData } from 'src/utils/format'; import { validateForm } from 'src/utils/validation'; """ * **Don't Do This:** * Scatter imports throughout the file or mix them haphazardly, as this can make it more difficult to understand the dependencies of the code. """typescript // Bad: Scattered imports import { Button } from './button'; import { component$ } from '@builder.io/qwik'; import { formatData } from 'src/utils/format'; import { useStore } from '@builder.io/qwik'; import { Input } from './input'; import { validateForm } from 'src/utils/validation'; """ * **Why:** Grouping imports improves code readability and makes it easier to identify the dependencies of the code at a glance. ### 7.3. Named Exports vs. Default Exports * **Do This:** * Primarily use named exports. They are more explicit and prevent naming collisions in larger projects. When the module is the component itself, the name should match the component name and the filename. """typescript // Good: Named export export const MyComponent = component$(() => { // ... }); """ * **Don't Do This:** * Avoid default exports unless there is one primary export from the module (e.g., a single utility function). It’s harder to trace where default exports originate. """typescript // Bad: Default export export default component$(() => { // ... }); """ * **Why:** Named exports make it clear what is being exported from a module, reducing ambiguity and potential naming conflicts. These code style and convention standards are designed to help Qwik developers write code that is consistent, readable, and maintainable. By following these guidelines, development teams can ensure that their codebases are easy to understand, debug, and extend, while also leveraging Qwik's unique features for optimal performance.
# API Integration Standards for Qwik This document outlines the coding standards for API integration within Qwik applications. It aims to provide clear guidelines for connecting to backend services and external APIs in an efficient, maintainable, and secure manner. These guidelines are specifically tailored for Qwik's unique architecture and focus on leveraging its features for optimal performance. ## 1. Architectural Patterns for API Integration Choosing the right architectural pattern is crucial for managing API interactions in a Qwik application. This choice impacts everything from maintainability to performance. ### 1.1. Server-Side Rendering (SSR) with API Proxy **Do This:** Implement an API proxy on your server to handle requests from the client, especially for sensitive data or operations. **Don't Do This:** Directly expose backend API endpoints to the client. **Why:** * **Security:** Protects API keys and backend infrastructure from direct client access. * **CORS Management:** Simplifies Cross-Origin Resource Sharing (CORS) configuration. The proxy can handle CORS headers instead of relying on the backend directly. * **Simplified API Consumption:** Transforms and aggregates data from multiple backend services into a single, streamlined API for the Qwik frontend. This reduces the complexity of the client-side code and improves maintainability. * **Caching:** Implements caching mechanisms at the proxy level to improve performance and reduce load on backend systems. """typescript // /src/routes/proxy/[...proxy].ts (Qwik City route) import { RequestHandler } from '@builder.io/qwik-city'; interface Env { API_BASE_URL: string; } export const onRequest: RequestHandler = async (event) => { const { request, params, env } = event; const apiBaseUrl = (env as Env).API_BASE_URL; // Access env vars properly if (!apiBaseUrl) { return event.json(500, { message: 'API_BASE_URL not configured' }); } const apiUrl = "${apiBaseUrl}/${params.proxy?.join('/')}"; // Construct the backend URL try { const apiResponse = await fetch(apiUrl, { method: request.method, headers: request.headers, body: request.body, }); if (!apiResponse.ok) { console.error('Error from backend:', apiResponse.status, apiResponse.statusText); return event.json(apiResponse.status, { message: "Backend error: ${apiResponse.statusText}" }); } // Forward the response from the backend const data = await apiResponse.json(); event.json(apiResponse.status, data); } catch (error: any) { console.error('Proxy error:', error); return event.json(500, { message: "Proxy error: ${error.message}" }); } }; """ **Explanation:** * This example uses Qwik City's "onRequest" handler to create a proxy route. * The "API_BASE_URL" should be set as an environment variable. **Important:** Access environment variables through "event.env" in Qwik City routes. * It forwards all requests (method, headers, body) to the backend API. * Error handling is crucial both for the "fetch" call and potential backend errors. Returning appropriate HTTP status codes and messages provides better debugging info. ### 1.2. Using "useEndpoint" for Server Functions **Do This:** Employ Qwik's "useEndpoint" for handling server-side logic in a type-safe way. **Don't Do This:** Make direct API calls from components in the browser that bypass server validation or authentication checks. **Why:** * **Type Safety:** "useEndpoint" provides end-to-end type safety from the client to the server, reducing runtime errors. * **Security:** Server functions are executed on the server, preventing client-side manipulation of critical logic or sensitive data. * **Qwik Optimization:** "useEndpoint" is designed to work seamlessly with Qwik's resumability, optimizing for performance. """typescript // src/components/MyForm.tsx import { component$, useStore, $, useContext, useTask } from '@builder.io/qwik'; import { useEndpoint, Form, routeAction$ } from '@builder.io/qwik-city'; interface FormData { name: string; email: string; } export const MyForm = component$(() => { const endpoint = useEndpoint<FormData>('/api/submit-form'); const store = useStore({ name: '', email: '', message: '' }); const submitForm = routeAction$(async (data: FormData, { fail, redirect }) => { try { const response = await fetch('/api/submit-form', { // Use relative URL for the endpoint method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }); if (!response.ok) { console.error('Server error:', response.status, response.statusText); return fail(response.status, 'Failed to submit form'); } redirect('/success'); } catch (e: any) { console.error('API Error', e); return fail(500, 'Form Submission Failed.'); } }); return ( <Form action={submitForm}> <label htmlFor="name">Name:</label> <input type="text" id="name" name="name" value={store.name} onChange$={(e) => (store.name = e.target.value)} /> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" value={store.email} onChange$={(e) => (store.email = e.target.value)} /> <button type="submit">Submit</button> </Form> ); }); """ """typescript // src/routes/api/submit-form/index.ts (API Endpoint) import { RequestHandler } from '@builder.io/qwik-city'; interface FormData { name: string; email: string; } export const onRequest: RequestHandler = async ({ request, json }) => { try { const data: FormData = await request.json(); if (!data.name || !data.email) { return json(400, { message: 'Name and email are required' }); } // Simulate API call console.log('Received form data:', data); return json(200, { message: 'Form submitted successfully' }); } catch (e) { console.error("Error processing form submission", e); return json(500, {message: "Internal server error."}); } }; """ **Explanation:** * The "MyForm" component uses a "routeAction$" to handle form submission and validation. On submission it POST's the form data to the "/api/submit-form" endpoint * The API Enpoint is defined in "src/routes/api/submit-form/index.ts". This is the actual route that will process the post request and is decoupled from the UI component. * This "routeAction$" ensures the POST happens using the endpoint you define. * Make sure your endpoint handles the data. ### 1.3. Edge Functions for Globally Distributed APIs **Do This:** Utilize edge functions (e.g., Cloudflare Workers, Netlify Functions) for geographically distributed APIs or computationally intensive tasks. **Don't Do This:** Rely solely on a centralized server when dealing with users distributed globally. **Why:** * **Low Latency:** Edge functions are executed closer to the user, reducing latency and improving the user experience. * **Scalability:** Edge functions can automatically scale to handle increased traffic. * **Reduced Load on Origin Server:** Offloads tasks such as image optimization, authentication, and A/B testing to the edge. """typescript // cloudflare/workers/my-edge-function.ts (Example Cloudflare Worker) export interface Env { // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ // MY_KV_NAMESPACE: KVNamespace; // // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ // MY_DURABLE_OBJECT: DurableObjectNamespace; // // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ // MY_BUCKET: R2Bucket; // // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ // MY_SERVICE: Fetcher; API_BASE_URL: string; } export default { async fetch( request: Request, env: Env, ctx: ExecutionContext ): Promise<Response> { const apiUrl = "${env.API_BASE_URL}/data"; // Access API URL from environment variable try { const apiResponse = await fetch(apiUrl, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!apiResponse.ok) { return new Response("API Error: ${apiResponse.status} ${apiResponse.statusText}", { status: apiResponse.status }); } const data = await apiResponse.json(); return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }); } catch (error: any) { console.error('Edge Function Error:', error); return new Response("Edge Function Error: ${error.message}", { status: 500 }); } }, }; """ **Explanation:** * This example demonstrates a simple Cloudflare Worker that fetches data from a backend API. * The "API_BASE_URL" is configured as an environment variable within the Cloudflare Worker's settings. * Error handling is crucial; the worker returns appropriate error messages and status codes. ## 2. Data Fetching Techniques Efficient data fetching is paramount in Qwik applications. Qwik's resumability features can be enhanced with proper data fetching strategies. ### 2.1. Leveraging "useClientEffect$" for Client-Side Fetching **Do This:** Use "useClientEffect$" when you need to fetch data on the client-side after hydration. This fetches the data once the component is active. **Don't Do This:** Fetch data synchronously during component initialization. **Why:** * **Performance:** Prevents blocking the initial rendering of the page. Data fetching happens in the background after the UI is interactive according to the end users needs * **Resumability:** "useClientEffect$" is designed to integrate with Qwik's resumability model. All fetching is driven by the client once the app is active - which speeds up development. """typescript // src/components/DataDisplay.tsx import { component$, useClientEffect$, useState } from '@builder.io/qwik'; interface DataItem { id: number; name: string; } export const DataDisplay = component$(() => { const [data, setData] = useState<DataItem[]>([]); const [loading, setLoading] = useState(false); // Add a loading state const [error, setError] = useState<string | null>(null); useClientEffect$(() => { setLoading(true); // Set loading to true when data fetching starts fetch('/api/data') .then(response => { if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } return response.json(); }) .then(data => { setData(data); }) .catch(e => { setError(e.message); }) .finally(() => { setLoading(false); // Set loading to false when fetching is complete }); }); return ( <> {loading && <p>Loading...</p>} {error && <p>Error: {error}</p>} <ul> {data.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ); }); """ **Explanation:** * "useClientEffect$" ensures that the "fetch" call is executed after the component is mounted on the client. * It includes error handling and a "loading" state for a better user experience. ### 2.2. Serializing Data with "server$" for Initial Render **Do This:** Use "server$" to pre-fetch and serialize data on the server during initial rendering, and then pass this serialized data to your Qwik components. **Don't Do This:** Fetch the same data redundantly on both the server and the client. **Why:** * **SEO Optimization:** Provides fully rendered content to search engine crawlers. * **Performance:** Reduces client-side loading time by providing pre-rendered content. Note: pre-rendering will trigger the API call every single time that route is hit. * **Resumability:** Qwik only needs to serialize the data and send it to the client. * **Accessibility:** A fully rendered page is immediately available to users, improving accessibility. """typescript // src/routes/my-page/index.tsx import { component$, useStore, server$, useTask$ } from '@builder.io/qwik'; import type { RequestHandler } from '@builder.io/qwik-city'; interface DataItem { id: number; name: string; } interface MyPageProps { initialData: DataItem[]; } export const useMyPageData = server$(async (): Promise<DataItem[]> => { // Simulate fetching data from an API const response = await fetch('https://jsonplaceholder.typicode.com/users'); if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data = await response.json(); // Transform the data to match the DataItem interface const transformedData = data.map((item: any) => ({ id: item.id, name: item.name, })); return transformedData; }); export const MyPage = component$((props: MyPageProps) => { const store = useStore({ data: props.initialData, }); return ( <> <h1>My Page</h1> <ul> {store.data.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ); }); // Qwik City Route Definition export const onRequest: RequestHandler = async ({ params, render, }) => { const initialData = await useMyPageData(); // Fetch data on the server const { html, } = await render({ base: '/', routes: [], symbols: [], prefetchStrategy: { progressive: false }, component: () => <MyPage initialData={initialData}/> }); return new Response(html(), { headers: { 'Content-Type': 'text/html; charset=utf-8' }, }); }; """ **Explanation:** * This example uses the "server$" function to fetch the data from a remote source. * It then assigns the data to static props on the server before the render, ensuring that the data is used to SSR the page. ### 2.3. Caching API Responses **Do This:** Implement caching mechanisms to reduce the number of API calls, especially for frequently accessed data. **Don't Do This:** Cache sensitive data without proper security considerations. **Why:** * **Performance:** Reduces latency and improves the responsiveness of the application. * **Cost Savings:** Reduces load on backend systems and lowers API usage costs. * **Offline Support:** Allows the application to function (to some extent) even when the user is offline. """typescript // Service worker (service-worker.ts) const CACHE_NAME = 'my-app-cache-v1'; const urlsToCache = [ '/', '/styles.css', '/script.js', ]; self.addEventListener('install', (event: any) => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); }); self.addEventListener('fetch', (event: any) => { event.respondWith( caches.match(event.request) .then(response => { if (response) { return response; // Return cached response } // If not cached, fetch from network return fetch(event.request).then( (response) => { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; } // IMPORTANT: Clone the response. A response is a stream // and because we want to use it twice we need to clone it. const responseToCache = response.clone(); caches.open(CACHE_NAME) .then((cache) => { cache.put(event.request, responseToCache); }); return response; } ); } ) ); }); """ **Explanation:** * This example demonstrates a basic service worker that caches API responses. * It intercepts "fetch" requests and checks if the requested resource is available in the cache. * If the resource is not cached, it fetches it from the network and caches the response. **Important Considerations for Caching:** * **Cache Invalidation:** Implement strategies for invalidating the cache when data changes. This could involve using cache busting techniques, time-based expiration, or webhooks. * **Security:** Be careful about caching sensitive data. Use appropriate encryption and access control mechanisms. * **Storage Limits:** Be aware of storage limits imposed by browsers and other caching mechanisms. ## 3. Error Handling and Resilience Robust error handling and resilience are crucial for building reliable Qwik applications. ### 3.1. Centralized Error Handling **Do This:** Implement a centralized error handling mechanism to catch and log errors from API calls. **Don't Do This:** Leave errors unhandled or handle them inconsistently throughout the application. **Why:** * **Maintainability:** Provides a single place to handle errors, making it easier to debug and maintain the application. * **User Experience:** Prevents the application from crashing and provides informative error messages to the user. * **Monitoring:** Allows you to track errors and identify potential problems. """typescript // src/utils/api.ts async function fetchData(url: string, options?: RequestInit) { try { const response = await fetch(url, options); if (!response.ok) { // Log the error to a monitoring service (e.g., Sentry) console.error("API Error: ${response.status} ${response.statusText} - URL: ${url}"); throw new Error("API Error: ${response.status} ${response.statusText}"); } return await response.json(); } catch (error: any) { // Handle the error globally console.error('Global API Error Handler:', error); throw error; // Re-throw the error to be handled by the component } } export { fetchData }; """ """typescript // src/components/MyComponent.tsx import { component$, useClientEffect$, useState } from '@builder.io/qwik'; import { fetchData } from '../utils/api'; interface DataItem { id: number; name: string; } export const MyComponent = component$(() => { const [data, setData] = useState<DataItem[]>([]); const [error, setError] = useState<string | null>(null); useClientEffect$(() => { fetchData('/api/data') .then(data => { setData(data); }) .catch(e => { setError(e.message); // Set the error message in the component's state }); }); return ( <> {error && <p>Error: {error}</p>} <ul> {data.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ); }); """ **Explanation:** * The "fetchData"utility function centralizes error handling for all API calls. * It logs errors to the console (or a monitoring service) and re-throws them so components can display informative messages to the user. * The component catches the error and displays an error message. ### 3.2. Retry Mechanisms **Do This:** Implement retry mechanisms with exponential backoff for transient API errors (e.g., network connectivity issues). **Don't Do This:** Retry indefinitely without any backoff strategy as this can overload the API. **Why:** * **Resilience:** Increases the resilience of the application to temporary network issues or server downtime. * **User Experience:** Reduces the likelihood of errors being displayed to the user. """typescript // src/utils/api.ts async function fetchDataWithRetry(url: string, options?: RequestInit, maxRetries = 3, backoffDelay = 1000) { let retryCount = 0; while (retryCount < maxRetries) { try { const response = await fetch(url, options); if (!response.ok) { if (response.status === 429) { // Handle the API response that indicates rate limiting console.warn('Rate limited. Waiting before retrying...'); await delay(backoffDelay * (2 ** retryCount)); // wait before retrying retryCount++; continue; // Continue to the next retry } console.error("API Error: ${response.status} ${response.statusText} - URL: ${url}"); throw new Error("API Error: ${response.status} ${response.statusText}"); } return await response.json(); } catch (error: any) { console.error("Attempt ${retryCount + 1} failed:", error); retryCount++; if (retryCount >= maxRetries) { console.error('Max retries reached. Failing request.'); throw error; // Re-throw the error to be handled by the component } // Wait before retrying with exponential backoff await delay(backoffDelay * (2 ** retryCount)); } } throw new Error('Max retries reached.'); // If the loop completes without returning } function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } export { fetchDataWithRetry }; """ **Explanation:** * The "fetchDataWithRetry" function implements a retry mechanism with exponential backoff. * It retries the API call up to "maxRetries" times. * The "backoffDelay" increases exponentially with each retry. ### 3.3. Circuit Breaker Pattern **Do This:** Implement a circuit breaker pattern to prevent cascading failures when a backend service is unavailable. **Don't Do This:** Continuously call a failing service without giving it a chance to recover. **Why:** * **Stability:** Prevents the application from being overwhelmed by failures in backend services. * **Resilience:** Allows backend services to recover without being bombarded by requests. """typescript // Example simplified circuit breaker in memory with an object: const circuitBreakers: { [key: string]: { state: 'open' | 'closed' | 'half-open', failureCount: number, lastFailure: number } } = {}; const MAX_FAILURES = 5; // Max failures before opening the circuit const RESET_TIMEOUT = 30000; // 30 seconds before attempting a reset async function fetchDataWithCircuitBreaker(url: string, options?: RequestInit) { if (!circuitBreakers[url]) { circuitBreakers[url] = { state: 'closed', failureCount: 0, lastFailure: 0 }; } const circuit = circuitBreakers[url]; // Check the circuit state if (circuit.state === 'open') { if (Date.now() - circuit.lastFailure < RESET_TIMEOUT) { throw new Error('Service unavailable (circuit open)'); } else { circuit.state = 'half-open'; // Attempt a reset } } try { const response = await fetch(url, options); if (!response.ok) { throw new Error("API Error: ${response.status} ${response.statusText}"); } const data = await response.json(); circuit.state = 'closed'; // Reset the circuit if successful circuit.failureCount = 0; return data; } catch (error: any) { circuit.failureCount++; circuit.lastFailure = Date.now(); if (circuit.failureCount >= MAX_FAILURES) { circuit.state = 'open'; // Open the circuit console.warn("Circuit opened for ${url}"); } throw error; } } // Example usage: async function getData() { try { const data = await fetchDataWithCircuitBreaker('/api/data'); console.log('Data:', data); } catch (error: any) { console.error('Error fetching data:', error.message); } } """ **Explanation:** * A simplified circuit breaker is implemented using an object "circuitBreakers". This should be replaced with a more robust solution for real-world applications (e.g., using a library or a dedicated service). * The circuit breaker has three states: "open", "closed", and "half-open". * In the "closed" state, requests are allowed through. If a request fails, the "failureCount" is incremented. If the "failureCount" exceeds "MAX_FAILURES", the circuit opens. * If the service hasn't been failing for the period as defined by "RESET_TIMEOUT" the circuit enters the "half-open" state, one request is allowed through to test the service. If the request is successful, the circuit closes. If it fails, the circuit opens again * When the service starts failing, it will enter the "open state" where no requests are allowed through. * When the circuit is "open", requests are immediately rejected, preventing further calls to the failing service. After a timeout period, the circuit enters the "half-open" state, allowing a limited number of requests to test the service. If the service recovers, the circuit closes. * While you could use this example, it is important to use a robust solution for real-world applications for managing a circuit breaker. ## 4. Security Considerations API integration introduces several security considerations. Following these principles is important to protect the application. ### 4.1. Input Validation **Do This:** Validate all input from the client before sending it to the backend API or storing it in the database. **Don't Do This:** Trust client-side data without validation. **Why:** * **Prevents Injection Attacks:** Protects against SQL injection, XSS, and other injection attacks. * **Data Integrity:** Ensures that data is consistent and accurate. * **Application Stability:** Prevents unexpected errors caused by invalid data. """typescript // src/routes/api/submit-form/index.ts import { RequestHandler } from '@builder.io/qwik-city'; interface FormData { name: string; email: string; message: string; } function isValidEmail(email: string): boolean { // Basic email validation regex (improve as needed) const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } export const onRequest: RequestHandler = async ({ request, json }) => { try { const data: FormData = await request.json(); // Validate input if (!data.name || data.name.length < 2) { return json(400, { message: 'Name must be at least 2 characters long' }); } if (!data.email || !isValidEmail(data.email)) { return json(400, { message: 'Invalid email address' }); } if (!data.message || data.message.length < 10) { return json(400, { message: 'Message must be at least 10 characters long' }); } // Simulate API call console.log('Received form data:', data); return json(200, { message: 'Form submitted successfully' }); } catch (e) { console.error("Error processing form submission", e); return json(500, {message: "Internal server error."}); } }; """ **Explanation:** * This example validates the "name", "email", and "message" fields from the form data. * It uses a regex to validate the email address. ### 4.2. Secure Authentication and Authorization **Do This:** Implement secure authentication and authorization mechanisms to protect API endpoints. **Don't Do This:** Rely on client-side authentication or authorization. **Why:** * **Data Protection:** Ensures that only authorized users can access sensitive data. * **System Integrity:** Protects against unauthorized modification or deletion of data. * **Compliance:** Meets regulatory requirements for data security. **Techniques:** * **JWT (JSON Web Tokens):** Use JWTs for authentication and authorization. Generate JWTs on the server after successful authentication. * **OAuth 2.0:** Implement OAuth 2.0 for authentication and authorization, especially when integrating with third-party services. * **Role-Based Access Control (RBAC):** Implement RBAC to restrict access to API endpoints based on user roles. """typescript // Example JWT Authentication (Simplified) import { sign, verify } from 'jsonwebtoken'; const JWT_SECRET = 'your-secret-key'; // Store this securely using env vars function generateToken(payload: any): string { return sign(payload, JWT_SECRET, { expiresIn: '1h' }); } function verifyToken(token: string): any { try { return verify(token, JWT_SECRET); } catch (error) { return null; } } // Example Route using JWT Authentication import { RequestHandler } from '@builder.io/qwik-city'; export const onRequestProtected: RequestHandler = async ({ request, json, headers }) => { const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return json(401, { message: 'Unauthorized: Missing or invalid token' }); } const token = authHeader.substring(7); // Remove 'Bearer ' const user = verifyToken(token); if (!user) { return json(401, { message: 'Unauthorized: Invalid token' }); } // Access user data from the verified token console.log('Authenticated user:', user); // ... your protected API logic here ... return json(200, { message: 'Protected resource accessed successfully' }); }; """ **Explanation:** * This is a *simplified* example. Use established libraries and patterns for JWT authentication in a production environment. * It generates a JWT token upon successful authentication. * The "verifyToken" function verifies the JWT token. * API routes that require authentication should check for a valid JWT token in the "Authorization" header. ### 4.3. Rate Limiting **Do This:** Implement rate limiting to prevent abuse and protect API endpoints from denial-of-service attacks. **Don't Do This:** Allow unlimited requests to API endpoints. **Why:** * **System Protection:** Protects against malicious attacks or unintentional overuse of API resources. * **Fair Usage:** Ensures that all users have fair access to API resources. * **Cost Management:** Reduces API usage costs. This is a critical pattern when developing any modern Qwik application. Please keep the above in mind when developing!
# Core Architecture Standards for Qwik This document outlines the core architectural standards for Qwik applications. It focuses on establishing a robust and maintainable foundation for Qwik projects, emphasizing key architectural patterns, project structure, and organization principles that align with Qwik's unique resumability and performance characteristics. This document aims to guide developers in building scalable, efficient, and maintainable Qwik applications. ## 1. Fundamental Architectural Patterns ### 1.1. Component-Based Architecture **Standard:** Embrace a component-based architecture where the application UI is decomposed into reusable and independent components. * **Do This:** Design interfaces and define clear contracts for components, promoting modularity and testability. * **Don't Do This:** Create monolithic components that handle excessive responsibilities and dependencies. **Why:** Component-based architecture promotes code reuse, simplifies testing, and enhances maintainability. **Example:** """tsx // Good: Small, focused component import { component$ } from '@builder.io/qwik'; export const Button = component$((props: { label: string, onClick$: () => void }) => { return <button onClick$={props.onClick$}>{props.label}</button>; }); // Bad: Large, complex component with multiple responsibilities export const ComplexComponent = component$(() => { // ... lots of logic and multiple UI elements return ( <div> {/* ... */} </div> ); }); """ ### 1.2. Reactive Programming **Standard:** Leverage Qwik's built-in reactivity features for managing application state and data flow. * **Do This:** Utilize signals ("useSignal", "useStore") effectively to create reactive data bindings. * **Don't Do This:** Directly manipulate the DOM or rely on global mutable state. **Why:** Reactive programming with signals enhances performance, reduces boilerplate, and makes the application more predictable. **Example:** """tsx // Correct usage of signals import { component$, useSignal } from '@builder.io/qwik'; export const Counter = component$(() => { const count = useSignal(0); return ( <div> <button onClick$={() => count.value++}>Increment</button> {count.value} </div> ); }); // Anti-pattern: Directly manipulating the DOM (Avoid) export const CounterBad = component$(() => { return ( <div> <button onClick$={() => { const element = document.getElementById('counter'); if (element) element.textContent = parseInt(element.textContent || '0') + 1 + "";} }>Increment</button> <span id='counter'>0</span> </div> ); }); """ ### 1.3. State Management **Standard:** Adopt a well-defined state management strategy, utilizing "useStore" for complex state requirements and "useSignal" for simple, transient state. * **Do This:** Organize store data in a structured manner that aligns with the application's data model. * **Don't Do This:** Overuse global state when component-level state suffices. **Why:** Centralized state management promotes data consistency, simplifies debugging, and improves the maintainability of complex applications. **Example:** """tsx // Using useStore (for Complex State) import { component$, useStore } from '@builder.io/qwik'; interface User { name: string; age: number; } export const UserProfile = component$(() => { const user = useStore<User>({ name: 'John Doe', age: 30, }); return ( <div> <p>Name: {user.name}</p> <p>Age: {user.age}</p> </div> ); }); """ ### 1.4. Resumability **Standard:** Leverage Qwik's core principle of resumability to enhance application performance and reduce initial load time. * **Do This:** Design the application to minimize the amount of JavaScript that needs to be downloaded and executed upfront. * **Don't Do This:** Load lots of unnecessary javascript which are not used on initial load. **Why:** Resumability significantly improves the user experience by providing faster initial page loads and improved time-to-interactive. **Example:** """tsx // Correct : The event handler is not serialized. It will only be bundled and transmitted to the browser when user performs interaction on this component in the browser. import { component$, useSignal } from '@builder.io/qwik'; export const DelayedCounter = component$(() => { const count = useSignal(0); return ( <div> <button onClick$={() => count.value++}>Increment</button> <span>{count.value}</span> </div> ); }); """ ### 1.5. Folder Structure **Standard:** Follow a clear and consistent project structure to organize components, services, and other application assets. * **Do This:** Organize code into logical folders like "components", "services", "routes", and "utils". * **Don't Do This:** Place all files in a single directory or use inconsistent naming conventions. **Why:** Consistent project structure enhances navigability and maintainability. **Example:** """ src/ ├── components/ # Reusable UI components │ ├── Button/ │ │ ├── Button.tsx │ │ └── Button.css │ └── Card/ │ ├── Card.tsx │ └── Card.css ├── pages/ # Route-specific components │ ├── index.tsx │ └── about.tsx ├── services/ # Business logic and data fetching │ └── api.ts ├── utils/ # Utility functions and helpers │ └── helpers.ts └── root.tsx # Root qwik component. Defines the sites layout """ ## 2. Project Organization Principles ### 2.1. Separation of Concerns **Standard:** Maintain a clear separation of concerns (SoC) between UI, logic, and data management. * **Do This:** Move business logic away from UI. Create dedicated services (e.g. "api.ts") for data fetching. * **Don't Do This:** Mix UI rendering with complex calculations or data manipulation. **Why:** SoC simplifies debugging, facilitates testing, and promotes code reuse. **Example:** """tsx // Good: Business logic separated from UI import { component$ } from '@builder.io/qwik'; import { fetchUserData } from './services/api'; export const UserProfile = component$(() => { const userData = fetchUserData(); // Asynchronous data fetching return ( <div> <p>Name: {userData.name}</p> <p>Age: {userData.age}</p> </div> ); }); // In services/api.ts export const fetchUserData = async () => { //Logic relating to fetching from API endpoint. return { name: 'Example', age: 25 }; }; // Bad: Mixing UI rendering with data manipulation export const UserProfileBad = component$(() => { const userData = {name: 'Example', age: 25}; //data hardcoded const processedData = "${userData.name} - ${userData.age}"; return ( <div> {processedData} </div> ); }); """ ### 2.2. Atomic Design **Standard:** Consider adopting the principles of Atomic Design to structure components. * **Do This:** Organize components into atoms, molecules, organisms, templates, and pages. * **Don't Do This:** Create ad-hoc component structures without a clear organizing principle. **Why:** Atomic Design provides a structured approach for building UIs, enhancing component reuse and maintainability. **Example:** """ components/ ├── atoms/ # Smallest, indivisible components (e.g., buttons, labels, inputs) │ ├── Button.tsx │ └── Input.tsx ├── molecules/ # Simple combinations of atoms (e.g., input with label) │ └── SearchBar.tsx ├── organisms/ # Relatively complex sections of the interface (e.g., a header) │ └── Header.tsx ├── templates/ # Page-level layouts that stitch organisms together │ └── HomeTemplate.tsx └── pages/ # Actual pages using templates └── index.tsx """ ### 2.3. Context API for Global Data **Standard:** Use the Context API for sharing data that is considered "global" for a tree of Qwik components. * **Do This:** Make a new context with "useContextProvider" to share data for components that are nested deeply. * **Don't Do This:** Share data by passing props through several layers of components ("prop drilling"). **Why:** Context API helps in ensuring maintainability for sharing "global" data in React applications. **Example:** """tsx //Create the context import { createContextId } from "@builder.io/qwik"; interface ThemeContextType { theme?: "light" | "dark"; setTheme?: (theme: ThemeContextType["theme"]) => void; } export const ThemeContext = createContextId<ThemeContextType>("theme-context"); //Wrap children with theme context import { component$, useContextProvider, useSignal } from "@builder.io/qwik"; import { ThemeContext } from "./theme-context"; export const ThemeProvider = component$((props: { children: any }) => { const themeSig = useSignal<ThemeContextType["theme"]>("light"); useContextProvider(ThemeContext, { theme: themeSig.value, setTheme: (theme) => { themeSig.value = theme; }, }); return props.children; }); //Consume theme from a deeply nested component import { component$, useContext } from "@builder.io/qwik"; import { ThemeContext } from "./theme-context"; export const DeeplyNestedComponent = component$(() => { const themeContext = useContext(ThemeContext); return ( <div> Theme: {themeContext.theme} <button onClick$={() => themeContext.setTheme?.(themeContext.theme === "light" ? "dark" : "light")}> Toggle Theme </button> </div> ); }); """ ## 3. Qwik-Specific Considerations ### 3.1 Optimizer usage **Standard:** Ensure that Qwik's optimizer is configured correctly to remove unused code, inline critical CSS, and perform other performance optimizations. * **Do This:** Use the Qwik CLI to initiate projects. This will automatically configures the optimizer. * **Don't Do This:** Skip this step when creating a production build. **Why:** The optimizer minimizes the bundle size, improves initial load performance, and reduces time to interactive (TTI). ### 3.2 Prefetching and lazy loading **Standard:** Strategically implement prefetching for critical resources and lazy loading for non-critical assets. * **Do This:** Employ "useClientEffect$" and "useVisibleTask$" to prefetch data or assets needed shortly after initial load. * **Don't Do This:** Eagerly load all assets upfront. **Why:** Prefetching and lazy loading optimize resource utilization and further enhance performance. **Example:** """tsx //Component is visible load data import { component$, useVisibleTask$ } from '@builder.io/qwik'; import { fetchData } from './services/api'; export const DataComponent = component$(() => { useVisibleTask$(async () => { const data = await fetchData(); // Fetch data when the component becomes visible // use the data }); return ( <div> {/* Render the Data */} </div> ); }); """ ### 3.3 Using Qwik City **Standard:** When building full-fledged applications, leverage Qwik City for routing, data loading, and server-side rendering (SSR). * **Do This:** Follow Qwik City's conventions for defining routes, layouts, and data endpoints. * **Don't Do This:** Manually handle routing and server-side logic in complex applications. **Why:** Qwik City automates common tasks, improving development speed, performance, and SEO. ### 3.4 File system routing **Standard:** Use the Qwik City's file system routing as a key aspect of the core architecture of the Qwik application. * **Do This:** Create files inside the "/routes" directory. These files will then be available as routes in the Qwik App. * **Don't Do This:** Create routes in any other way other than creating files inside the "/routes" directory and expect the routes to work. **Why:** File system routing allows developers to create routes in a Qwik application using the file system, rather than manual route creation. **Example:** """text src/ └── routes/ ├── index.tsx # -> / ├── about/ # -> /about/ └── contact.tsx # -> /contact """ ## 4. Security Best Practices ### 4.1. Secure Coding Practices **Standard:** Adhere to secure coding practices to prevent common web vulnerabilities. * **Do This:** Sanitize user inputs, implement proper authentication and authorization, and prevent cross-site scripting (XSS) and cross-site request forgery (CSRF) attacks. * **Don't Do This:** Trust user input without validation or expose sensitive information in client-side code. **Why:** Security vulnerabilities can compromise the application's integrity and user data. ### 4.2. Environment Variables **Standard:** Securely manage environment variables to avoid exposing sensitive information in source code. * **Do This:** Use ".env" files for development environments and configure environment variables in production deployments. * **Don't Do This:** Hard-code API keys or passwords directly in the source code. **Why:** Environment variables help keep sensitive data separate from the code base. ### 4.3. Dependency Management **Standard:** Manage dependencies effectively and keep packages up to date. * **Do This:** Use a package manager (e.g., npm, yarn, pnpm) to manage dependencies and regularly update packages to address security vulnerabilities. * **Don't Do This:** Use outdated or unmaintained packages. **Why:** Vulnerable dependencies can introduce security risks. ## 5. Common Anti-Patterns * **Over-reliance on Global State:** Avoid using global state excessively. Prefer component-level state or context when possible. * **Direct DOM Manipulation:** Avoid directly manipulating the DOM. Use Qwik's reactive features instead. * **Ignoring Qwik’s Resumability:** Neglecting to optimize for resumability can negate many of Qwik's performance benefits. * **Complex Components:** Large, overly complex components are difficult to maintain and test. Break them down into smaller, reusable parts. * **Inconsistent Folder Structure:** A disorganized folder structure makes it difficult to navigate and maintain the codebase. ## 6. Continuous Improvement This document will be updated periodically to reflect the latest Qwik features, best practices, and community standards. Contributors are encouraged to suggest improvements and refinements to ensure this document remains a valuable resource for Qwik developers.
# Component Design Standards for Qwik This document outlines the coding standards for component design in Qwik, aiming to promote maintainability, reusability, and performance. Adhering to these guidelines ensures efficient collaboration and a robust codebase in accordance with the latest Qwik features and best practices. ## 1. Component Architecture ### 1.1. Component Hierarchy and Structure * **Do This:** Favor a component-based architecture with a clear hierarchy. Break down complex UI into smaller, reusable components. * **Don't Do This:** Create monolithic components that handle too many responsibilities. **Why:** A well-defined component hierarchy simplifies maintenance and promotes reusability. Breaking down complex UIs enables focus on specific features and improve readability. It also encourages efficient code splitting and lazy loading in Qwik. **Example:** """typescript // Good: Separated concerns // card.tsx import { component$, $, useContext } from '@builder.io/qwik'; import { ThemeContext } from './theme-context'; export const Card = component$(() => { const theme = useContext(ThemeContext); return ( <div class={"card ${theme.value}"}> <CardHeader /> <CardBody /> </div> ); }); // card-header.tsx import { component$ } from '@builder.io/qwik'; export const CardHeader = component$(() => { return ( <header> <h3>Card Title</h3> </header> ); }); // card-body.tsx import { component$ } from '@builder.io/qwik'; export const CardBody = component$(() => { return ( <div> <p>Card content goes here.</p> </div> ); }); // theme-context.tsx import { createContextId } from '@builder.io/qwik'; export const ThemeContext = createContextId<'light' | 'dark'>('theme'); """ """typescript // Bad: Monolithic component import { component$ } from '@builder.io/qwik'; export const MonolithicCard = component$(() => { return ( <div class="card"> <header> <h3>Card Title</h3> </header> <div> <p>Card content goes here.</p> </div> </div> ); }); """ ### 1.2. Smart vs. Dumb Components (Presentational vs. Container Components) * **Do This:** Distinguish between smart (container) and dumb (presentational) components. Smart components handle data fetching, state management, and business logic. Dumb components primarily focus on rendering UI based on props. * **Don't Do This:** Mix data handling and UI rendering logic within a single component. **Why:** This separation enhances reusability, testability, and maintainability. Presentational components become easily adaptable and restylable for different contexts, and container components facilitate easier state management and testing. **Example:** """typescript // Smart Component (Container) - data fetching import { component$, useStore, useTask$ } from '@builder.io/qwik'; import { PresentationalList } from './presentational-list'; export const SmartList = component$(() => { const state = useStore({ items: [] }); useTask$(async ({ track }) => { track(() => state); // Re-run when state changes const fetchData = async () => { const response = await fetch('/api/items'); state.items = await response.json(); }; fetchData(); }); return <PresentationalList items={state.items} />; }) // Dumb Component (Presentational) - UI rendering import { component$ } from '@builder.io/qwik'; interface Props { items: any[]; } export const PresentationalList = component$<Props>(({ items }) => { return ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ); }); """ ### 1.3. State Management Strategy * **Do This:** Favor Qwik's "useStore" and "useSignal" for local component state. "useContext" when sharing state between components that may not neccessarily be parent and child. Consider more advanced state management solutions like Redux or Zustand (though typically not needed) only for complex applications with global state needs. * **Don't Do This:** Over-engineer state management with complex solutions for simple component state. Mutations should be predictable and avoid hidden side-effects. **Why:** Using Qwik's built-in state management tools ("useStore", "useSignal", "useContext", and "useTask$") simplifies development and provides performance benefits due to Qwik's resumability feature. External state management libraries introduce additional complexity and may not align with Qwik's core principles. **Example:** """typescript // Using useStore import { component$, useStore } from '@builder.io/qwik'; export const Counter = component$(() => { const state = useStore({ count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick$={() => state.count++}>Increment</button> </div> ); }); """ """typescript // Using useContext import { component$, useContext, createContextId } from '@builder.io/qwik'; const MyContext = createContextId<{ value: string }>('my-context'); export const ProviderComponent = component$(() => { return ( <MyContext.Provider value={{ value: 'Hello from context!' }}> <ConsumerComponent /> </MyContext.Provider> ); }); export const ConsumerComponent = component$(() => { const contextValue = useContext(MyContext); return <p>{contextValue.value}</p>; }); """ ### 1.4. Prop Drilling Avoidance * **Do This:** Favor "useContext" to share state with deep child components instead of passing props down through multiple levels. * **Don't Do This:** Prop drilling, particularly when the intermediate components do not utilize prop values. **Why:** Reduces the complexity and coupling of intermediate components, simplifying code and improving maintainability. Context offers a direct way for deeply nested components to access needed data. **Example:** (refer to the "useContext" example above) ## 2. Component Implementation ### 2.1. Functional Components * **Do This:** Use functional components with Qwik's "component$" for all new components. Embrace immutability when working with props and state. * **Don't Do This:** Use class-based components or mutable data structures without a clear reason. **Why:** Functional components with hooks are simpler to read, write, and test. They promote immutability which makes state management easier and avoids unexpected side effects. **Example:** """typescript import { component$, useSignal } from '@builder.io/qwik'; export const MyComponent = component$(() => { const name = useSignal('Initial Name'); return ( <div> <p>Hello, {name.value}!</p> <input type="text" value={name.value} onInput$={(event) => (name.value = (event.target as HTMLInputElement).value)} /> </div> ); }); """ ### 2.2. Props and Events * **Do This:** Define prop types clearly using interfaces or types. Use Qwik's "Prop<T>" to ensure type safety. Design events to be predictable and consistent. Use "$" to define event handlers. * **Don't Do This:** Avoid "any" type for props. Don't introduce side effects within event handlers without proper handling. **Why:** Type safety prevents runtime errors and improves code maintainability. Clear event handling reduces unexpected behavior and improves component predictability. **Example:** """typescript import { component$, Prop } from '@builder.io/qwik'; interface Props { name: string; age: number; onClick: Prop<() => void>; // Correctly typed event handler } export const UserProfile = component$<Props>(({ name, age, onClick }) => { return ( <div> <p>Name: {name}</p> <p>Age: {age}</p> <button onClick$={onClick}>Click Me</button> </div> ); }); """ """typescript // Usage import { component$ } from '@builder.io/qwik'; import { UserProfile } from './user-profile'; export const ParentComponent = component$(() => { const handleClick = () => { alert('Button clicked!'); }; return <UserProfile name="John Doe" age={30} onClick={handleClick} />; }); """ ### 2.3. Code-Splitting * **Do This:** Utilize Qwik's automatic code-splitting capabilities by breaking down larger components into smaller, lazy-loaded modules, keeping components small and focus on a specific task. * **Don't Do This:** Bundle all component code into a single large file, which can hurt initial load times. **Why:** Code splitting can improve the user experience for initial load times. **Example:** """typescript // Dynamically import components import { component$, Dynamic } from '@builder.io/qwik'; export const ParentComponent = component$(() => { return ( <div> <p>Parent Component</p> <Dynamic import={() => import('./lazy-component')} /> </div> ); }); // lazy-component.tsx import { component$ } from '@builder.io/qwik'; export default component$(() => { return <p>Lazy Loaded Component!</p>; }); """ ### 2.4. Styling * **Do This:** Use CSS Modules, Styled Components with Qwik Optimizer, or other CSS-in-JS solutions for component-specific styling. Scope styles to components to avoid naming conflicts. * **Don't Do This:** Use global CSS styles that can inadvertently affect other parts of the application. **Why:** Component-specific styling enhances maintainability and prevents unexpected style collisions. Use CSS Modules or CSS-in-JS to encapsulate styles within components. **Example (CSS Modules):** """typescript // my-component.tsx import { component$ } from '@builder.io/qwik'; import styles from './my-component.module.css'; export const MyComponent = component$(() => { return ( <div class={styles.container}> <h1 class={styles.title}>Hello, Qwik!</h1> </div> ); }); // my-component.module.css .container { background-color: #f0f0f0; padding: 20px; } .title { color: #333; } """ ### 2.5. Accessibility * **Do This:** Ensure components are accessible by providing proper semantic HTML, ARIA attributes, and keyboard navigation. Test components with screen readers. * **Don't Do This:** Ignore accessibility considerations, leading to components unusable by people with disabilities. **Why:** Accessibility is crucial for creating inclusive applications. Providing correct semantic structure and ARIA attributes ensures screen readers and other assistive technologies can properly interpret the content. **Example:** """typescript import { component$ } from '@builder.io/qwik'; export const AccessibleButton = component$(() => { return ( <button aria-label="Close dialog" onClick$={() => alert('Button clicked!')}> Close </button> ); }); """ ### 2.6. Naming Conventions * **Do This:** Use PascalCase for component names (e.g., "MyComponent"). Use camelCase for props and event handlers (e.g., "onClick", "userName"). * **Don't Do This:** Use inconsistent or cryptic names that obscure the component's purpose. **Why:** Consistent naming conventions improve code readability and maintainability. **Example:** """typescript import { component$ } from '@builder.io/qwik'; interface Props { userName: string; onClick: () => void; } export const UserProfileCard = component$<Props>(({ userName, onClick }) => { return ( <div> <p>Welcome, {userName}!</p> <button onClick$={onClick}>View Profile</button> </div> ); }); """ ### 2.7. Component Composition * **Do This:** Leverage component composition techniques to create flexible components. Use the "children" prop for content projection. * **Don't Do This:** Create rigid components that are difficult to adapt to changing requirements. **Why:** Component composition facilitates code reuse and allows components to be configured in various ways. **Example:** """typescript import { component$, Slot } from '@builder.io/qwik'; export const Card = component$(() => { return ( <div class="card"> <div class="card-header"> <Slot name="header" /> </div> <div class="card-body"> <Slot /> </div> <div class="card-footer"> <Slot name="footer" /> </div> </div> ); }); // Usage: import { component$ } from '@builder.io/qwik'; import { Card } from './card'; export const MyPage = component$(() => { return ( <Card> This is the card body content. <div q:slot="header"> <h2>Card Title</h2> </div> <div q:slot="footer"> <button>Save</button> </div> </Card> ); }); """ ## 3. Performance Optimization ### 3.1. Minimizing Re-renders * **Do This:** Use "useClientEffect$" and "useTask$" judiciously to minimize re-renders. Track only necessary dependencies to avoid triggering unnecessary updates. Freeze props to prevent changes. * **Don't Do This:** Rely on naive state updates that cause frequent re-renders. **Why:** Minimizing re-renders improves application's overall responsiveness. "useClientEffect$" is only executed in the client and can execute code that depends on the rendered state. **Example:** """typescript import { component$, useSignal, useTask$ } from '@builder.io/qwik'; export const OptimizedComponent = component$(() => { const count = useSignal(0); useTask$(({ track }) => { // Track only the count value track(() => count.value); console.log('Count updated:', count.value); }); return ( <div> <p>Count: {count.value}</p> <button onClick$={() => count.value++}>Increment</button> </div> ); }); """ ### 3.2. Serialization * **Do This:** Properly serialize data passed as props to ensure resumability. Avoid passing complex, non-serializable data. * **Don't Do This:** Pass functions or complex objects that cannot be serialized, which violates Qwik's resumability principle. **Why:** Serialization is crucial for Qwik's resumability, which allows the application to pause and resume on the client. **Example:** """typescript import { component$ } from '@builder.io/qwik'; interface Props { message: string; // Serializable data } export const SerializableComponent = component$<Props>(({ message }) => { return <p>{message}</p>; }); """ ### 3.3. Lazy Loading with "useVisibleTask$" * **Do This:** Use "useVisibleTask$" for tasks that should only start when the component is visible, preventing unnecessary computations before the component is needed. * **Don't Do This:** Perform heavy computations on component initialization regardless of visibility. **Why:** "useVisibleTask$" only starts when the component is visible, avoiding unnecessary computations and improving initial load times. """typescript import { component$, useVisibleTask$ } from '@builder.io/qwik'; export const VisibleComponent = component$(() => { useVisibleTask$(({ track }) => { // Track relevant state track(() => true); // Example, replace with actual dependency console.log('Component is visible!'); }); return <p>Visible Component</p>; }); """ ## 4. Testing ### 4.1. Component Tests * **Do This:** Write unit tests for individual components to ensure they render correctly and behave as expected. Use snapshot testing to catch unexpected UI changes. Integration testing is still necessary but is less relevant to this standard. * **Don't Do This:** Skip component tests, relying solely on end-to-end tests. **Why:** Unit tests provide rapid feedback on component behavior, making it easier to identify and fix bugs early in the development process. **Example:** (using Jest and Qwik's testing utilities) """typescript // my-component.test.tsx import { render } from '@builder.io/qwik/testing'; import { MyComponent } from './my-component'; describe('MyComponent', () => { it('should render correctly', async () => { const { container } = await render(<MyComponent />); expect(container.textContent).toContain('Hello, Qwik!'); }); }); """ ### 4.2. Types and Contracts * **Do This:** use Typescript and type checking to make sure integration is secure. Follow a test-driven approach. * **Don't Do This:** Avoid Typescript or testing because of time constraints. **Why:** Avoid errors and ensure integration. Save time in debugging. ## 5. Security ### 5.1. Input Validation * **Do This:** Validate all user inputs within components to prevent security vulnerabilities such as cross-site scripting (XSS) and injection attacks. * **Don't Do This:** Trust user inputs without validation, leading to potential security exploits. **Why:** Input validation is crucial for preventing malicious data from entering your application. **Example:** """typescript import { component$, useSignal } from '@builder.io/qwik'; export const ValidatedInput = component$(() => { const inputValue = useSignal(''); const sanitizedValue = useSignal(''); const handleChange = (event: Event) => { const input = (event.target as HTMLInputElement).value; // Sanitize input value (example: remove HTML tags) const sanitized = input.replace(/<[^>]*>/g, ''); inputValue.value = input; sanitizedValue.value = sanitized; }; return ( <div> <input type="text" value={inputValue.value} onInput$={handleChange} /> <p>Original Input: {inputValue.value}</p> <p>Sanitized Input: {sanitizedValue.value}</p> </div> ); }); """ ### 5.2. Secure Data Handling * **Do This:** Use secure methods for storing and transmitting sensitive data. Avoid storing sensitive data in local storage or cookies without proper encryption * **Don't Do This:** Expose sensitive data directly in the client-side code. **Why:** Protecting sensitive data is paramount. Secure data handling practices are essential for maintaining user privacy and security. ### 5.3. Avoiding Server-Side Rendering Vulnerabilities * **Do This:** Sanitize data properly during SSR to avoid injection vulnerabilities. * **Don't Do This:** Trust all data during SSR. **Why:** When using SSR, the server needs to be just as secured as the client against vulnerabilities.
# State Management Standards for Qwik This document outlines the best practices for managing state in Qwik applications. Proper state management is crucial for building performant, maintainable, and scalable Qwik applications. It covers approaches to managing application state, controlling data flow, and leveraging Qwik's reactivity features. These standards are based on the latest Qwik features and recommendations. ## 1. Core Principles of State Management in Qwik ### 1.1. Location Strategy **Standard:** Choose the appropriate scope and location for your state. State should live as close as possible to the components that need it, but elevated to a shared parent when multiple components need to access and update the same data. Aim for a balanced approach to avoid over-centralization or unnecessary prop drilling. **Why:** Efficient state management in Qwik hinges on locality. Local state simplifies component logic and reduces the chances of unnecessary re-renders. Global state, while convenient, should be reserved for data truly needed across the entire application. **Do This:** * Use component-level state with "useStore()" when the state is only relevant to that component. * Elevate state to a shared parent component when multiple child components need to share the state. * Use global state sparingly, typically only for application-wide configurations or user authentication. **Don't Do This:** * Avoid excessive prop drilling, passing state down through multiple layers of components. This leads to tightly coupled and difficult-to-maintain code. * Don't store component-specific state in a global store, causing unnecessary updates across the entire application. * Abuse global state. Over-reliance on global state increases coupling and makes it hard to reason about data flow. **Example (Component-Level):** """tsx import { component$, useStore } from '@builder.io/qwik'; export const Counter = component$(() => { const store = useStore({ count: 0, increment: () => { store.count++; }, }); return ( <> <p>Count: {store.count}</p> <button onClick$={store.increment}>Increment</button> </> ); }); """ **Example (Shared Parent):** """tsx import { component$, useStore, $, useContext, createContextId } from '@builder.io/qwik'; interface UserContextType { username: string; updateUsername: (newUsername: string) => void; } const UserContext = createContextId<UserContextType>('user-context'); export const UserProvider = component$((props: { children: any }) => { const store = useStore({ username: "Guest", updateUsername: $((newUsername: string) => { store.username = newUsername; }) }); return ( <UserContext.Provider value={store}> {props.children} </UserContext.Provider> ); }); const UsernameDisplay = component$(() => { const user = useContext(UserContext); return <p>Username: {user.username}</p>; }); const UsernameUpdater = component$(() => { const user = useContext(UserContext); const handleInputChange = $((event: Event) => { const inputElement = event.target as HTMLInputElement; user.updateUsername(inputElement.value); }); return ( <input type="text" value={user.username as string} onInput$={handleInputChange} /> ); }); export const App = component$(() => { return ( <UserProvider> <UsernameDisplay /> <UsernameUpdater /> </UserProvider> ); }); """ ### 1.2. Immutability **Standard:** Treat state as immutable whenever possible. Instead of modifying state directly, create new copies with the desired changes. **Why:** Immutability simplifies debugging, enables efficient change detection, and prevents unexpected side effects. Qwik's reactivity system works best with immutable data. **Do This:** * Use the spread operator ("...") or "Object.assign()" to create new objects with updated properties. * Use array methods like "map()", "filter()", and "slice()" to create new arrays instead of modifying them directly. * Consider using immutable data structures from libraries like Immer for complex state updates. **Don't Do This:** * Directly modify properties of state objects (e.g., "state.property = newValue"). * Use array methods like "push()", "pop()", "splice()", or "sort()" that mutate the original array. **Example (Immutable Update):** """tsx import { component$, useStore } from '@builder.io/qwik'; interface Item { id: number; name: string; completed: boolean; } export const ItemList = component$(() => { const store = useStore<{ items: Item[] }>({ items: [{ id: 1, name: 'Learn Qwik', completed: false }], }); const toggleComplete = $((id: number) => { store.items = store.items.map((item) => item.id === id ? { ...item, completed: !item.completed } : item ); }); return ( <ul> {store.items.map((item) => ( <li key={item.id}> <label> <input type="checkbox" checked={item.completed} onChange$={() => toggleComplete(item.id)} /> {item.name} </label> </li> ))} </ul> ); }); """ ### 1.3. Serialization **Standard:** Ensure that your state can be serialized. Qwik relies on serialization for resumability. **Why:** Qwik serializes the state of your application to HTML, which is then used to "resume" the application on the client. Non-serializable state will be lost when the application is resumed, leading to unexpected behavior. **Do This:** * Store only primitive values (strings, numbers, booleans), plain objects, and arrays in your state. * Avoid storing classes, functions, or instances of custom objects directly in the state. * If you need to store complex data, serialize it to a string or a primitive value before storing it in the state. **Don't Do This:** * Store DOM elements or other non-serializable objects directly in the state. * Store functions or closures in the state. * Assume that anything you put into the state will survive a page reload without proper serialization. **Example (Serialization):** """tsx import { component$, useStore } from '@builder.io/qwik'; interface SerializableData { name: string; value: number; } export const DataComponent = component$(() => { const store = useStore<{ data: string | null }>({ data: localStorage.getItem('myData') || null, }); const updateData = $((newData: SerializableData) => { const serializedData = JSON.stringify(newData); store.data = serializedData; localStorage.setItem('myData', serializedData); }); return ( <> <button onClick$={() => updateData({ name: 'Example', value: 42 })}> Update Data </button> <p>Data: {store.data}</p> </> ); }); """ ## 2. Qwik's State Management Tools ### 2.1. "useStore()" **Standard:** Utilize "useStore()" for managing component-level state. **Why:** "useStore()" provides a simple and efficient way to create reactive state within a component. It automatically tracks changes to the state and triggers re-renders when necessary. It also supports serialization for resumability. **Do This:** * Use "useStore()" to create the initial state within a component. * Update the state by modifying the properties of the store object. * Use the "$()" function wrap code that modifies the store to ensure proper tracking of mutations. **Don't Do This:** * Try use other React hooks for state management. Qwik's model avoids the use of the virtual DOM, and therefore useState, useEffect etc. are incompatible. * Forget to wrap mutation logic within "$()". This is critical for Qwik's reactivity. **Example:** """tsx import { component$, useStore, $ } from '@builder.io/qwik'; export const MyComponent = component$(() => { const store = useStore({ name: 'Initial Name', updateName: $((newName: string) => { store.name = newName; }), }); return ( <> <p>Name: {store.name}</p> <button onClick$={() => store.updateName('New Name')}>Update Name</button> </> ); }); """ ### 2.2. "$()" and Event Handlers **Standard:** Utilize "$()" to wrap event handlers and functions that modify state and are not directly invoked in the JSX. **Why:** "$()" tells Qwik to serialize and lazy load the wrapped code. This is essential for reducing the initial JavaScript payload and improving performance. **Do This:** * Wrap all event handlers with "$()" when they are not directly used in JSX. * Wrap functions that are called from event handlers and modify state with "$()". * Consider using "useClientEffect$" (and similar client-only hooks) to run code only on the client, if hydration is not immediately necessary. **Don't Do This:** * Forget to wrap event handlers with "$()", especially when they modify the state. This can lead to hydration issues and break resumability. * Overuse "$()" for code that doesn't need to be serialized or lazy-loaded. **Example:** """tsx import { component$, useStore, $ } from '@builder.io/qwik'; export const MyComponent = component$(() => { const store = useStore({ message: 'Initial Message', updateMessage: $((newMessage: string) => { store.message = newMessage; }), }); const handleClick = $(() => { store.updateMessage('New Message from Click'); }); return ( <> <p>Message: {store.message}</p> <button onClick$={handleClick}>Update Message</button> </> ); }); """ ### 2.3 "useContextProvider" and "useContext" **Standard:** When state needs to be shared between components that are not direct parent/child, use Qwik's context API. **Why:** Like React, the context API allows sharing reactive state throughout a component tree without prop drilling. Also, unlike React's "useContext", Qwik's "useContext" hook can be used server-side. **Do This:** * Create a context ID using "createContextId()". * Use "useContextProvider()" to provide a value from a parent component, at the right level within your application. * Use "useContext()" to consume data from the context in any descendent. **Don't Do This:** * Overuse context. When state can be simply passed using props, do so. Excessive context makes reasoning about the state within a component harder. * Modify context values directly. Follow the principal of immutability to make debugging and reasoning about state easier. **Example:** """tsx // context.ts import { createContextId } from '@builder.io/qwik'; export const MyContext = createContextId<{ value: string }>('my-context'); // component.tsx import { component$, useContextProvider, useContext, useStore } from '@builder.io/qwik'; import { MyContext } from './context'; export const ParentComponent = component$((props: { children: any }) => { // You can also pass existing server state into the context. const store = useStore({ value: 'Server Name' }); useContextProvider(MyContext, store); return props.children; }); export const ChildComponent = component$(() => { const myContext = useContext(MyContext); return <p>Value from context: {myContext.value}</p>; }); // App.tsx export const App = component$(() => { return (<ParentComponent><ChildComponent /></ParentComponent>) }); """ ## 3. Data Fetching and State ### 3.1. "useResource$()" **Standard:** Utilize "useResource$()" for asynchronous data fetching. **Why:** "useResource$()" integrates seamlessly with Qwik's resumability system and provides a reactive way to manage the loading and error states of data fetching operations on the server. **Do This:** * Use "useResource$()" to wrap asynchronous functions that fetch data. * Access the data using the ".value" property of the resource. * Handle loading and error states gracefully. **Don't Do This:** * Perform data fetching directly within components without using "useResource$()". * Ignore error states, resulting in unhandled exceptions and a poor user experience. **Example:** """tsx import { component$, useResource$ } from '@builder.io/qwik'; export const DataFetcher = component$(() => { const dataResource = useResource$(async () => { const response = await fetch('https://jsonplaceholder.typicode.com/todos/1'); if (!response.ok) { throw new Error('Failed to fetch data'); } return await response.json(); }); return ( <> {dataResource.value ? ( <p>Data: {dataResource.value.title}</p> ) : dataResource.error ? ( <p>Error: {dataResource.error.message}</p> ) : ( <p>Loading...</p> )} </> ); }); """ ### 3.2. Caching Strategies **Standard:** Implement appropriate caching strategies for fetched data. **Why:** Caching reduces the number of API calls, improves performance, and reduces load on the server. **Do This:** * Use browser caching (e.g., "Cache-Control" headers) for static assets and API responses. * Use a client-side caching library (e.g., "lru-cache") for frequently accessed data. * Implement server-side caching using technologies like Redis or Memcached for data that is shared across multiple users. * Incorporate stale-while-revalidate for near-instant loading with background updates. **Don't Do This:** * Cache sensitive data without proper security measures. * Cache data indefinitely without a proper expiration strategy. * Ignore cache invalidation, resulting in stale data being displayed to the user. **Example (Client-Side Caching):** While the client might directly implement with "localStorage" as shown previously. """typescript import { cache } from '@builder.io/qwik'; const fetchData = cache( async (url: string) => { const res = await fetch(url); return res.json(); }, { maxAge: 60 * 60 * 1000, // 1 hour } ); export const MyComponent = component$(() => { const dataResource = useResource$(async () => { return await fetchData('https://jsonplaceholder.typicode.com/todos/1'); }); return ( <> {dataResource.value ? ( <p>Data: {dataResource.value.title}</p> ) : dataResource.error ? ( <p>Error: {dataResource.error.message}</p> ) : ( <p>Loading...</p> )} </> ); }); """ ## 4. Global State Management ### 4.1. When to Use Global State **Standard:** Use global state only when necessary. **Why:** Global state increases the complexity of the application and makes it harder to reason about data flow. It should be reserved for data that is truly needed across the entire application. **Do This:** * Use global state for application-wide configuration settings. * Use global state for user authentication status and user profile information. * Consider using a global store for managing shared data that is accessed by multiple components. * Qwik prefers the use of server data and "useContext" to manage dependencies. **Don't Do This:** * Use global state for component-specific data. * Overuse global state, resulting in tight coupling and difficult-to-maintain code. ### 4.2. Global Context Injection **Standard:** When global state is necessary, use Qwik's context to inject data into components. **Why:** Context allows you to provide data to a subtree of components without having to manually pass props at every level. It provides server and client capabilities out of the box. **Do This:** * Define a context using "createContextId()". * Provide the context value using "useContextProvider()" at the root of the application or at the appropriate level in the component tree. * Consume the context value using "useContext()" in the components that need it. **Don't Do This:** * Modify the context value directly, which can lead to unexpected behavior. * Create too many contexts, resulting in a complex and difficult-to-manage application. **Example (Global Context):** """tsx // app-context.ts import { createContextId } from '@builder.io/qwik'; export interface AppContextType { theme: 'light' | 'dark'; toggleTheme: () => void; } export const AppContext = createContextId<AppContextType>('app-context'); // app.tsx import { component$, useContextProvider, useStore, $, useContext } from '@builder.io/qwik'; import { AppContext } from './app-context'; export const App = component$((props: { children: any }) => { const store = useStore<AppContextType>({ theme: 'light', toggleTheme: $((() => { store.theme = store.theme === 'light' ? 'dark' : 'light'; }), }); useContextProvider(AppContext, store); return ( <> <ThemeProvider>{props.children}</ThemeProvider> </> ); }); const ThemeProvider = component$((props: { children: any }) => { const context = useContext(AppContext); return ( <div class={context.theme}> {props.children} </div> ) }) """ ## 5. Advanced State Management Patterns ### 5.1. State Machines **Standard:** Consider using state machines for managing complex state transitions. **Why:** State machines provide a structured and predictable way to manage complex state logic. They can help to prevent invalid state transitions and simplify debugging. **Do This:** * Use a state machine library like "xstate" to define the states, events, and transitions of your application. * Integrate the state machine with Qwik's reactivity system to trigger re-renders when the state changes. **Don't Do This:** * Attempt to manage complex state logic manually, which can lead to bugs and difficult-to-maintain code. * Overuse state machines for simple state logic that can be easily managed with "useStore()". ### 5.2. Signals (Experimental) **Standard:** Signals are still considered experimental within Qwik. Use with caution. **Why:** Signals offer a fine-grained reactivity system that can improve performance by minimizing unnecessary re-renders. They allow you to track changes to individual values within a store, rather than the entire store object. **Do This:** * Evaluate them where performance is critical. * Carefully measure performance differences between "useStore()" and signals before committing to a large-scale refactoring. **Don't Do This:** Rely on them in production without thorough testing. Use signals without understanding their implications of Qwik's resumability system. ## 6. Security Considerations ### 6.1. Preventing XSS Attacks **Standard:** Sanitize data before storing it in the state. **Why:** Storing unsanitized data in the state can make your application vulnerable to cross-site scripting (XSS) attacks. If an attacker can inject malicious JavaScript code into the state, they can potentially steal user data, hijack user sessions, or deface your application. **Do This:** * Sanitize user input using a library like DOMPurify before storing it in the state. * Use Qwik's built-in escaping mechanisms to prevent XSS attacks. **Don't Do This:** * Store unsanitized user input directly in the state. * Disable Qwik's built-in escaping mechanisms without understanding the security implications. ### 6.2. Protecting Sensitive Data **Standard:** Protect sensitive data stored in the state. **Why:** Sensitive data, such as passwords, API keys, and user authentication tokens, should be protected from unauthorized access. Storing sensitive data in the wrong place can expose it to potential attacks. **Do This:** * Avoid storing sensitive data in the client-side state whenever possible. * If you must store sensitive data in the client-side state, encrypt it using a strong encryption algorithm. * Store sensitive data on the server and access it through secure APIs. **Don't Do This:** * Store sensitive data in plain text in the client-side state. * Expose sensitive data in URLs or in the browser's history. ## 7. Testing ### 7.1 Unit Testing **Standard:** Write unit tests for components that manage state. **Why:** State management logic can become quite complex, so automated tests are critical for ensuring the reliability of your application. To test a Qwik component with state: **Do This:** * Ensure the state updates as expected when the component interacts with outside services. * Confirm that the UI reflects the state, after any updates or changes. You can use tools like "jest", "vitest", or "playwright" to test Qwik components. ### 7.2 Component Integration Testing **Standard:** Implement component integration tests for complete stateful components. **Why:** Although unit tests are important, integration tests test a bigger piece of the application, specifically the parts responsible for state management. **Do This:** * Make sure that user input updates app state. * Confirm that any async calls update app state properly. * Verify that the system behaves reproducibly for a specific set of inputs and preconditions. ## 8. Conclusion Effective state management is critical for building robust and maintainable Qwik applications. By following these coding standards, developers can ensure that their applications are performant, secure, and easy to understand. By adhering to these principles, Qwik developers can leverage the framework's key strengths, ensuring excellent performance and a smooth development experience.