| name | solidjs |
| description | [Applies to: **/*.{js,jsx}] This guide provides definitive, opinionated best practices for writing high-performance, maintainable SolidJS applications, focusing on reactivity, component architecture, and common pitfalls. |
| source | cursor_mdc |
solidjs Best Practices
This document outlines the definitive best practices for developing with SolidJS 1.x. Adhere to these guidelines to leverage Solid's fine-grained reactivity, ensure optimal performance, and maintain a consistent, scalable codebase.
1. Use SolidJS 1.x (Stable)
Always use the current stable 1.x release. SolidJS 2.0 is experimental and not ready for production. Avoid any features or APIs mentioned for 2.0.
2. Component Architecture & Code Organization
2.1. Component File Naming
Use PascalCase for component filenames. This clearly distinguishes components from other modules.
- ❌ BAD:
mybutton.jsx, user-profile.jsx
- ✅ GOOD:
MyButton.jsx, UserProfile.jsx
2.2. Keep Components Lean
Components should be small and focused. Extract complex logic or UI parts into separate components or custom primitives.
2.3. Colocate Styles
Use Vite's CSS Modules for component-specific styling. This provides scoped styles without runtime overhead.
3. Props Handling
SolidJS props are reactive getters. Incorrect handling breaks reactivity.
3.1. Never Destructure Props Directly
Do not destructure props at the component's top level. This immediately loses reactivity. Access props via props.propertyName.
- ❌ BAD:
function MyComponent({ name, age }) {
return <div>{name} is {age}</div>;
}
- ✅ GOOD:
function MyComponent(props) {
return <div>{props.name} is {props.age}</div>;
}
3.2. Apply Default Props with mergeProps
Use mergeProps for default props. This preserves reactivity and merges objects non-destructively.
- ❌ BAD:
function MyComponent(props) {
const name = props.name || "Guest";
return <div>Hello, {name}</div>;
}
- ✅ GOOD:
import { mergeProps } from 'solid-js';
function MyComponent(props) {
const merged = mergeProps({ name: "Guest", greeting: "Hello" }, props);
return <div>{merged.greeting}, {merged.name}!</div>;
}
3.3. Use splitProps for Spreading
When spreading props, use splitProps to separate known props from rest props. This is crucial for accessibility and avoiding prop conflicts.
- ❌ BAD:
function MyInput(props) {
return <input type="text" {...props} />;
}
- ✅ GOOD:
import { splitProps } from 'solid-js';
function MyInput(props) {
const [local, rest] = splitProps(props, ["label", "onChange"]);
return (
<label>
{local.label}
<input type="text" onChange={local.onChange} {...rest} />
</label>
);
}
4. State Management
4.1. Signals for Local State
Use createSignal for simple, primitive, or local component state. It's the most performant and idiomatic way to manage reactive values.
4.2. Stores for Complex/Global State
Use createStore for nested, mutable objects or global state. Stores provide deep reactivity for objects.
- ❌ BAD:
createSignal({ user: { name: "John" } }) (nested updates are not reactive without manual spreading)
- ✅ GOOD:
import { createStore } from 'solid-js/store';
const [user, setUser] = createStore({ firstName: "John", lastName: "Doe" });
function UserProfile() {
return (
<div>
<p>Name: {user.firstName} {user.lastName}</p>
<button onClick={() => setUser("firstName", "Jane")}>Change Name</button>
</div>
);
}
5. Side Effects & Lifecycle
5.1. createEffect for Side Effects
Encapsulate all side effects in createEffect. Solid automatically tracks dependencies, ensuring effects run only when necessary.
- ❌ BAD:
console.log(mySignal()); outside createEffect (runs once)
- ✅ GOOD:
import { createSignal, createEffect } from 'solid-js';
function Logger() {
const [value, setValue] = createSignal("initial");
createEffect(() => {
console.log("Value changed:", value());
});
return <input onInput={(e) => setValue(e.target.value)} value={value()} />;
}
5.2. onCleanup for Resource Management
Use onCleanup to dispose of resources (subscriptions, timers, event listeners). It runs when the reactive scope (component, effect) is destroyed.
- ❌ BAD: Global
clearInterval or removeEventListener without onCleanup.
- ✅ GOOD:
import { createEffect, onCleanup } from 'solid-js';
function Timer() {
createEffect(() => {
const interval = setInterval(() => console.log("tick"), 1000);
onCleanup(() => clearInterval(interval));
});
return <div>Timer running...</div>;
}
6. Performance Considerations
6.1. Avoid Unnecessary Re-renders
Solid's fine-grained reactivity minimizes re-renders, but be mindful of creating new functions or objects inside JSX that aren't memoized.
6.2. Lazy Loading
Use dynamic imports for route-level components. This reduces initial bundle size.
- ✅ GOOD:
import { lazy } from 'solid-js';
import { Routes, Route } from '@solidjs/router';
const HomePage = lazy(() => import('./pages/Home'));
const AboutPage = lazy(() => import('./pages/About'));
function AppRoutes() {
return (
<Routes>
<Route path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
</Routes>
);
}
7. Ecosystem Tools
Leverage official and community-vetted tools.
- Bundling:
vite-plugin-solid
- Routing:
solid-router (or SolidStart)
- Head Management:
solid-meta
- Linting:
eslint-plugin-solid
- Testing:
solid-testing-library
8. Accessibility
Prioritize semantic HTML and WAI-ARIA attributes. SolidJS does not abstract away the DOM, making direct accessibility implementation straightforward.
9. Testing Approaches
Use solid-testing-library for unit and integration tests. It provides a user-centric API similar to React Testing Library.
- ✅ GOOD:
import { render, screen } from 'solid-testing-library';
import { createSignal } from 'solid-js';
import MyComponent from './MyComponent';
test('MyComponent displays the correct count', async () => {
const [count, setCount] = createSignal(0);
render(() => <MyComponent count={count()} />);
expect(screen.getByText(/Count: 0/i)).toBeInTheDocument();
setCount(1);
await Promise.resolve();
expect(screen.getByText(/Count: 1/i)).toBeInTheDocument();
});