# 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>
Increment
);
});
"""
**Example (Shared Parent):**
"""tsx
import { component$, useStore, $, useContext, createContextId } from '@builder.io/qwik';
interface UserContextType {
username: string;
updateUsername: (newUsername: string) => void;
}
const UserContext = createContextId('user-context');
export const UserProvider = component$((props: { children: any }) => {
const store = useStore({
username: "Guest",
updateUsername: $((newUsername: string) => {
store.username = newUsername;
})
});
return (
{props.children}
);
});
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 (
);
});
export const App = component$(() => {
return (
);
});
"""
### 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 (
{store.items.map((item) => (
toggleComplete(item.id)}
/>
{item.name}
))}
);
});
"""
### 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 (
<>
updateData({ name: 'Example', value: 42 })}>
Update Data
<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>
store.updateName('New Name')}>Update Name
);
});
"""
### 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>
Update Message
);
});
"""
### 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 ()
});
"""
## 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('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({
theme: 'light',
toggleTheme: $((() => {
store.theme = store.theme === 'light' ? 'dark' : 'light';
}),
});
useContextProvider(AppContext, store);
return (
<>
{props.children}
);
});
const ThemeProvider = component$((props: { children: any }) => {
const context = useContext(AppContext);
return (
{props.children}
)
})
"""
## 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.
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.
# Performance Optimization Standards for Qwik This document outlines the coding standards for performance optimization in Qwik applications. It serves as a guide for developers to build efficient, responsive, and resource-friendly Qwik applications. These standards leverage Qwik's unique features, focusing on resumability and progressive hydration, to achieve optimal performance. ## 1. Core Principles of Qwik Performance Optimization Qwik's performance model revolves around these core principles: * **Resumability:** Serialize the application's state and execution context, pause execution on the server, and immediately resume it on the client. This avoids re-downloading and re-execution of code that has already been executed on the server, resulting in faster time-to-interactive (TTI). * **Lazy-loading:** Load code only when it's needed. Qwik automatically delays the download and execution of JavaScript code until user interaction necessitates it. * **Progressive Hydration (fine-grained):** Unlike traditional hydration, which re-executes the entire component tree on the client, Qwik allows for fine-grained hydration of individual interactive components. This minimizes the amount of JavaScript that needs to be executed during initial page load. ## 2. Architectural Optimization ### 2.1. Embracing Qwik's Resumability * **Standard:** Design application architecture around Qwik's resumability model. Think of your application as a set of independent resumable components. * **Do This:** Structure the application into smaller, independent components that can be individually serialized and resumed. * **Don't Do This:** Create large, monolithic components that are difficult to serialize and resume. * **Why:** Resumability dramatically improves TTI by avoiding unnecessary code download and execution. * **Example:** """typescript // Good: Modular component import { component$, useSignal } from '@builder.io/qwik'; export const Counter = component$(() => { const count = useSignal(0); return ( <button onClick$={() => count.value++}> Count: {count.value} </button> ); }); // Bad: Giant component with lots of nested logic (harder to resume efficiently) export const BigComponent = component$(() => { let state = useSignal({...}); return ( //... lots of complex JSX and logic interwoven here ); }); """ ### 2.2. Server-Side Rendering (SSR) by Default * **Standard:** Leverage Qwik City's SSR capabilities for improved initial load performance and SEO. * **Do This:** Build the application using Qwik City for seamless SSR integration. * **Don't Do This:** Opt-out of SSR without a strong reason (e.g., internal application with no SEO requirements). * **Why:** SSR delivers pre-rendered HTML to the browser, improving initial paint time and SEO. * **Example:** Qwik City automatically handles SSR for routes defined in the "src/routes" directory. No extra code is needed to achieve SSR. However, ensure all data fetching and business logic is SSR-compatible. ### 2.3. Route-Based Code Splitting * **Standard:** Structure the application into routes, each with its own code bundle. * **Do This:** Use Qwik City's routing system to define clear boundaries between routes. * **Don't Do This:** Load unnecessary code for other routes on the initial page load. * **Why:** Code splitting improves initial load performance by reducing the size of the initial JavaScript bundle. """typescript // src/routes/index.tsx import { component$ } from '@builder.io/qwik'; export default component$(() => { return ( <h1>Welcome to the Home Page!</h1> ); }); // src/routes/about/index.tsx import { component$ } from '@builder.io/qwik'; export default component$(() => { return ( <h1>About Us</h1> ); }); """ ## 3. Component-Level Optimization ### 3.1. Minimizing Component Complexity * **Standard:** Keep components small and focused with a single responsibility. * **Do This:** Decompose large components into smaller, reusable components. * **Don't Do This:** Write large, complex components with many responsibilities. * **Why:** Smaller components are easier to serialize, resume, and optimize. They also promote code reusability. """typescript // Good: Small, focused component import { component$ } from '@builder.io/qwik'; interface Props { message: string; } export const Message = component$((props: Props) => { return ( <p>{props.message}</p> ); }); // Bad: Large, complex component export const ComplexComponent = component$(() => { // ... lots of logic and JSX return ( // Very verbose JSX ); }); """ ### 3.2. Smart use of "useSignal" and "$()" * **Standard:** Use "useSignal" appropriately to manage reactive state, and understand the performance implications of where you use "$". * **Do This:** Use signals for local component state that needs to trigger updates. Delay the inclusion of "$" until ABSOLUTELY NECESSARY to avoid over-eager bundling. * **Don't Do This:** Overuse signals when simple variables would suffice, or prematurely include "$". * **Why:** Signals provide fine-grained reactivity, but creating too many signals can add overhead. Delaying "$" ensures functions are only serialized and downloaded if they're actually needed for interactivity. """typescript // Good: Signal for count, $() added only when a click handler references it. import { component$, useSignal } from '@builder.io/qwik'; export const Counter = component$(() => { const count = useSignal(0); // Reactive state return ( <button onClick$={() => count.value++}> {/* Only HERE is when we need download/execution */} Count: {count.value} </button> ); }); //Bad - Adding $() is the top level unnecessarily. import { component$, useSignal } from '@builder.io/qwik'; const increment = $(() => { //Unnecessary $ here! This becomes a global function instead of being scoped to the component. count.value++; }); export const Counter = component$(() => { const count = useSignal(0); // Reactive state return ( <button onClick$={increment}> Count: {count.value} </button> ); }); """ ### 3.3. Effective Use of "useTask$" * **Standard:** Employ "useTask$" for side effects and data fetching that should only run on the client after the component is resumed. * **Do This:** Use "useTask$" for operations like DOM manipulation, event listeners, or fetching data from client-side APIs. * **Don't Do This:** Use "useTask$" for operations that can be performed during SSR, or for purely synchronous calculations, or outside of Qwik components. * **Why:** "useTask$" ensures that these operations are only executed after the component has been resumed in the browser, preventing unnecessary execution on the server. """typescript // Good: Fetching data on the client import { component$, useTask$, useSignal } from '@builder.io/qwik'; export const DataFetcher = component$(() => { const data = useSignal<any>(null); useTask$(async () => { const response = await fetch('/api/data'); data.value = await response.json(); }); return ( <div> {data.value ? <pre>{JSON.stringify(data.value, null, 2)}</pre> : 'Loading...'} </div> ); }); // Bad: Performing synchronous calculations import { component$, useTask$ } from '@builder.io/qwik'; export const SynchronousCalculation = component$(() => { useTask$(() => { // This is unnecessary, do this outside of useTask$ directly in the component const result = 2 + 2; console.log(result); }); return ( <div>Calculations run in client!</div> ); }); """ ### 3.4. Managing Large Lists Efficiently (Virtualization) * **Standard:** Employ virtualization techniques when rendering large lists to avoid performance bottlenecks. * **Do This:** Use libraries such as "qwik-virtual" or other virtualized list components. * **Don't Do This:** Render large lists directly without virtualization, as this can lead to performance issues. * **Why:** Virtualization renders only the visible items, significantly reducing the amount of DOM that needs to be created and updated especially if there are event handlers. """typescript // Example with basic virtualization (simplified) import { component$, useSignal, $, useTask$ } from '@builder.io/qwik'; interface Item { id: number; name: string; } export const VirtualizedList = component$(() => { const items = useSignal<Item[]>([]); const visibleItems = useSignal<Item[]>([]); const startIndex = useSignal(0); const itemsToRender = 20; useTask$(async ({track}) => { track(() => startIndex.value); //Simulate data loading await new Promise(resolve => setTimeout(resolve, 500)); const newData = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: "Item ${i}" })); items.value = newData; visibleItems.value = items.value.slice(startIndex.value, startIndex.value + itemsToRender); }); const handleScroll = $(() => { // Basic scroll handling const newStartIndex = Math.floor(window.scrollY / 50); // Example calculation based on item height if (newStartIndex !== startIndex.value) { startIndex.value = newStartIndex; visibleItems.value = items.value.slice(startIndex.value, startIndex.value + itemsToRender); //Recalculat visible items } }); useTask$(({track}) => { window.addEventListener('scroll', handleScroll); track(handleScroll); return () => { // Cleanup window.removeEventListener('scroll', handleScroll); }; }); return ( <div > {visibleItems.value.map((item) => ( <div key={item.id}>{item.name}</div> ))} </div> ); }); """ ## 4. Data Fetching Optimization ### 4.1. Efficient Data Loading Strategies * **Standard:** Optimize data fetching strategies based on specific needs. Qwik supports various strategies. * **Do This:** Use "server$" for initial data loading during SSR. Also use "useClientEffect$" (or similar) to trigger subsequent client-side data refreshing, use "client" mark function to do lazy data fetching. * **Don't Do This:** Fetch all data upfront without considering deferring non-critical data. Mixing up the client and server could cause unexpected results. * **Why:** Efficient data loading balances server-side rendering and client-side fetching and interactivity, preventing waterfall requests and minimizing data transfer. """typescript // Server-side data fetching import { component$, server$, useSignal } from '@builder.io/qwik'; interface Data { id: number; name: string; } const fetchData = server$(async (): Promise<Data[]> => { // Simulate fetching data from a database await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate network latency return [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }]; }); export const DataComponent = component$(() => { const data = useSignal<Data[]>([]); useTask$(async () => { data.value = await fetchData(); }); return ( <ul> {data.value.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ); }); """ ### 4.2. Caching and Memoization * **Standard:** Implement caching and memoization to avoid redundant data fetching and calculations. * **Do This:** Use caching mechanisms like "localStorage" or "sessionStorage" for frequently accessed data. Implement memoization for expensive calculations using "useMemo". * **Don't Do This:** Over-cache data that may become stale quickly. Fail to memoize expensive calculations that are re-executed frequently. * **Why:** Caching and memoization reduce the load on the server and improve the responsiveness of the application. """typescript // Memoization example import { component$, useMemo, useSignal } from '@builder.io/qwik'; export const MemoizedCalculation = component$(() => { const input = useSignal(2); const expensiveCalculation = useMemo(() => { console.log('Calculating...'); // This will only log when input.value changes let result = input.value; for (let i = 0; i < 100000000; i++) { // Simulate expensive calculation result = Math.sin(result); } return result; }, [input.value]); return ( <div> <input type="number" value={input.value} onChange$={(event) => input.value = parseFloat(event.target.value)} /> <p>Result: {expensiveCalculation}</p> </div> ); }); """ ### 4.3. Optimizing API Requests * **Standard:** Optimize API requests to minimize data transfer and reduce latency. * **Do This:** Use efficient data formats like JSON. Implement data compression (e.g., gzip). Use a CDN to serve static assets and API responses. Debounce and throttle API requests. * **Don't Do This:** Request unnecessary data fields. Send uncompressed data over the network. Make excessive API requests. * **Why:** Optimizing API requests improves the performance of data-driven applications and reduces network bandwidth consumption. ## 5. Image Optimization ### 5.1. Using Optimized Image Formats and Compression * **Standard:** Serve images in optimized formats (WebP, AVIF) and apply appropriate compression techniques. * **Do This:** Use tools like "squoosh" or image optimization plugins during the build process. * **Don't Do This:** Serve large, unoptimized images, leading to increased page load times. * **Why:** Optimized images reduce page weight and improve loading speed. """bash # Example Squoosh CLI usage (replace with your actual paths) npx @squoosh/cli --webp auto -d ./optimized-images ./images """ ### 5.2. Implementing Lazy Loading * **Standard:** Lazy load images below the fold. * **Do This:** Use the "loading="lazy"" attribute on "<img>" tags. * **Don't Do This:** Load all images upfront, even if they are not immediately visible. * **Why:** Lazy loading improves initial page load performance by deferring the loading of non-critical images. """html <img src="image.jpg" alt="Description" loading="lazy" /> """ ### 5.3. Responsive Images * **Standard:** Serve different image sizes based on the user's device and screen resolution. * **Do This:** Use the "<picture>" element and "srcset" attribute on "<img>" tags. * **Don't Do This:** serve same large image to all devices. * **Why:** Responisve images ensure that users download only the necessary image size for their devices, reducing bandwidth consumption and improving load times. """html <picture> <source media="(max-width: 600px)" srcset="image-small.jpg"> <source media="(max-width: 1200px)" srcset="image-medium.jpg"> <img src="image-large.jpg" alt="Description"> </picture> """ ## 6. Third-Party Libraries ### 6.1 Evaluate the impact of third party libraries * **Standard:** Carefully evaluate the performance impact of third-party libraries before including them in an Qwik project. * **Do This:** Research the library's size, dependencies, and performance characteristics. Consider using alternatives with smaller footprints or implementing the required functionality manually. * **Don't Do This:** Blindly import libraries without assessing their potential impact on app performance. * **Why:** Third-party libraries can significantly increase the download size, parsing time, and execution time of a web application, negatively affecting its overall performance. ### 6.2 Lazy-Loading Third-Party Components * **Standard:** Use dynamic imports to lazy-load third-party components and libraries. * **Do This:** Load components on-demand when they are needed, to avoid including unnecessary code in the initial bundle. * **Don't Do This:** Include third-party components directly in the main bundle, as this increases the initial load time. ## 7. Auditing and Monitoring ### 7.1. Using Performance Analysis Tools * **Standard:** Regularly audit and monitor the performance of Qwik applications using tools. * **Do This:** Use Chrome DevTools, Lighthouse, PageSpeed Insights, and WebPageTest to identify performance bottlenecks and areas for improvement. * **Don't Do This:** Neglect performance analysis, leading to undetected performance regressions. * **Why:** Performance analysis tools provide valuable insights into the performance characteristics of Qwik applications. * **Chrome DevTools:** Use the Performance tab to record and analyze runtime performance. * **Lighthouse:** Generate reports with scores for performance, accessibility, best practices, SEO, and PWA. * **PageSpeed Insights:** Analyze the page's performance and provides suggestions for optimization. * **WebPageTest:** Test website performance from multiple locations and browsers. ### 7.2. Setting Performance Budgets and Alerts * **Standard:** Establish performance budgets & setup alerts. * **Do This:** Set performance budgets for key metrics (e.g., initial load time, TTI, First Contentful Paint) and configure alerts to notify developers when budgets are exceeded. * **Don't Do This:** Overlook maintain performance budgets. * **Why:** Performance budgets and alerts ensure that Qwik applications meet performance goals and that performance regressions are detected and addressed promptly. These standards provide a framework for building high-performance Qwik applications. By following these guidelines, developers can create applications that are fast, responsive, and efficient. Remember to constantly evaluate and refine these standards based on new features and best practices emerging in the Qwik ecosystem. Regularly checking the official Qwik documentation and community resources is crucial for staying up-to-date.