Skip to main content
Glama
web-development.mdc15.1 kB
--- description: "Modern web development: JavaScript, TypeScript, React, Vue, Next.js, Node.js, HTML/CSS, and web APIs" globs: ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.vue", "**/*.svelte", "**/*.html", "**/*.css", "**/*.scss", "**/package.json", "**/next.config.*", "**/vite.config.*", "**/tsconfig.json"] alwaysApply: false --- # Web Development Patterns Modern web development best practices for JavaScript, TypeScript, and related frameworks. ## CRITICAL: Project Setup Workflow ### ALWAYS Use CLI Tools for New Projects **WRONG approach (creates incomplete/broken projects):** ``` 1. Create package.json manually 2. Create files one by one 3. Copy component code from documentation 4. Forget to run npm install ``` **CORRECT approach:** ```bash # Next.js npx create-next-app@latest my-app --typescript --tailwind --app --eslint # React (Vite) npm create vite@latest my-app -- --template react-ts # Vue npm create vue@latest # Then for component libraries: cd my-app npx shadcn-ui@latest init npx shadcn-ui@latest add button card dialog ``` ### Version Checking Before Setup ``` <think> Before creating a project, I MUST: 1. Check current stable versions 2. Verify CLI options are up to date 3. Check for compatibility issues </think> → web_search("create-next-app latest version options December 2024") → web_search("shadcn-ui Next.js 15 compatibility 2024") ``` --- ## JavaScript Best Practices ### Modern Syntax - Use `const` by default, `let` when reassignment needed, never `var` - Prefer arrow functions for callbacks and short functions - Use template literals for string interpolation - Destructure objects and arrays - Use spread operator for immutable operations ```javascript // Modern patterns const { name, email } = user; const updatedList = [...items, newItem]; const greeting = `Hello, ${name}!`; const fetchData = async () => { /* ... */ }; ``` ### Async Patterns - Prefer `async/await` over `.then()` chains - Always handle errors with try/catch - Use `Promise.all()` for parallel operations - Use `Promise.allSettled()` when some failures are acceptable ```javascript // Parallel operations const [users, posts] = await Promise.all([ fetchUsers(), fetchPosts() ]); // Error handling try { const data = await fetchData(); } catch (error) { console.error('Fetch failed:', error.message); throw new DataFetchError(error); } ``` ### Array Methods - `map()` for transformations - `filter()` for selection - `reduce()` for aggregation - `find()` for single item lookup - Avoid mutating methods on shared data --- ## TypeScript Best Practices ### Type Definitions - Use interfaces for object shapes - Use types for unions, intersections, and computed types - Prefer `unknown` over `any` - Use `as const` for literal types ```typescript // Interface for objects interface User { id: string; name: string; email: string; role: 'admin' | 'user'; } // Type for unions type Result<T> = { success: true; data: T } | { success: false; error: string }; // Generic constraints function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } ``` ### Type Safety - Enable strict mode in tsconfig - Avoid type assertions unless necessary - Use generics for reusable code - Leverage inference when types are obvious ### Utility Types ```typescript Partial<T> // All properties optional Required<T> // All properties required Pick<T, K> // Select specific properties Omit<T, K> // Exclude specific properties Record<K, V> // Object with keys K and values V ``` --- ## React Patterns ### Component Structure ```tsx // Recommended component structure interface Props { // Props interface at top } export function ComponentName({ prop1, prop2 }: Props) { // Hooks first const [state, setState] = useState(); const ref = useRef(); // Derived values const computed = useMemo(() => /* ... */, [deps]); // Effects useEffect(() => { /* ... */ }, [deps]); // Handlers const handleClick = () => { /* ... */ }; // Render return (/* JSX */); } ``` ### Hooks Best Practices #### useState ```tsx // Group related state const [form, setForm] = useState({ name: '', email: '' }); // Use updater function for derived state setCount(prev => prev + 1); ``` #### useEffect ```tsx // Cleanup subscriptions useEffect(() => { const subscription = subscribe(); return () => subscription.unsubscribe(); }, []); // Avoid object dependencies useEffect(() => { // This runs on every render if options is new object }, [options]); // Bad const { limit, offset } = options; useEffect(() => { // This only runs when primitives change }, [limit, offset]); // Good ``` #### useMemo & useCallback ```tsx // Memoize expensive computations const sorted = useMemo( () => items.sort((a, b) => a.value - b.value), [items] ); // Memoize callbacks passed to children const handleClick = useCallback(() => { doSomething(id); }, [id]); ``` ### State Management - **Local state**: useState, useReducer - **Lifted state**: Prop drilling for 1-2 levels - **Context**: Theme, auth, shared global state - **External stores**: Zustand, Redux for complex state ### Performance - Use React.memo for expensive child components - Virtualize long lists (react-window, react-virtuoso) - Code split with React.lazy and Suspense - Avoid inline object/function props --- ## Next.js Patterns (App Router) ### File Conventions ``` app/ layout.tsx # Root layout page.tsx # Home page loading.tsx # Loading UI error.tsx # Error boundary not-found.tsx # 404 page api/ # API routes route.ts (group)/ # Route groups (no URL impact) @modal/ # Parallel routes [...slug]/ # Catch-all segments ``` ### Server Components (default) ```tsx // app/posts/page.tsx - Server Component async function PostsPage() { const posts = await fetchPosts(); // Direct data fetching return <PostList posts={posts} />; } ``` ### Client Components ```tsx 'use client'; import { useState } from 'react'; export function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; } ``` ### Server Actions ```tsx // actions.ts 'use server'; export async function createPost(formData: FormData) { const title = formData.get('title'); await db.posts.create({ title }); revalidatePath('/posts'); } ``` ### Data Fetching ```tsx // Parallel data fetching async function Page() { const [user, posts] = await Promise.all([ getUser(), getPosts() ]); } // With caching const data = await fetch(url, { next: { revalidate: 3600 } // Cache for 1 hour }); ``` --- ## Vue.js Patterns (Composition API) ### Component Structure ```vue <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; // Props const props = defineProps<{ title: string; count?: number; }>(); // Emits const emit = defineEmits<{ update: [value: string]; }>(); // Reactive state const isOpen = ref(false); const items = ref<Item[]>([]); // Computed const total = computed(() => items.value.length); // Lifecycle onMounted(() => { fetchItems(); }); // Methods function toggle() { isOpen.value = !isOpen.value; } </script> <template> <div>{{ title }}</div> </template> ``` ### Composables ```typescript // composables/useFetch.ts export function useFetch<T>(url: string) { const data = ref<T | null>(null); const error = ref<Error | null>(null); const loading = ref(true); fetch(url) .then(res => res.json()) .then(json => { data.value = json; }) .catch(err => { error.value = err; }) .finally(() => { loading.value = false; }); return { data, error, loading }; } ``` --- ## Node.js Patterns ### Express/Fastify Structure ```typescript // Route handler app.get('/users/:id', async (req, res) => { try { const user = await userService.findById(req.params.id); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json(user); } catch (error) { next(error); // Pass to error handler } }); // Error handling middleware app.use((error, req, res, next) => { console.error(error); res.status(500).json({ error: 'Internal server error' }); }); ``` ### Async Error Handling ```typescript // Wrapper for async handlers const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; app.get('/users', asyncHandler(async (req, res) => { const users = await getUsers(); res.json(users); })); ``` ### Environment Configuration ```typescript // config.ts import { z } from 'zod'; const envSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']), PORT: z.string().transform(Number).default('3000'), DATABASE_URL: z.string().url(), }); export const env = envSchema.parse(process.env); ``` --- ## Chart.js Best Practices ### CRITICAL: Container Height Requirements **WRONG (causes infinite expansion):** ```html <!-- Chart will expand infinitely! --> <div> <canvas id="myChart"></canvas> </div> <script> new Chart(ctx, { options: { responsive: true, maintainAspectRatio: false // Without fixed height = BROKEN } }); </script> ``` **CORRECT:** ```html <!-- Fixed height container --> <div style="height: 400px; position: relative;"> <canvas id="myChart"></canvas> </div> <!-- With Tailwind --> <div class="h-96 relative"> <canvas id="myChart"></canvas> </div> <script> new Chart(ctx, { options: { responsive: true, maintainAspectRatio: false // OK because container has height } }); </script> ``` **Alternative (maintain aspect ratio):** ```html <div class="w-full"> <canvas id="myChart"></canvas> </div> <script> new Chart(ctx, { options: { responsive: true, maintainAspectRatio: true, aspectRatio: 2 // Width:Height ratio } }); </script> ``` ### Chart Initialization Checklist ``` □ Container has fixed height OR maintainAspectRatio: true □ Canvas is inside a positioned container (position: relative) □ Chart.js loaded before initialization □ Canvas element exists in DOM before new Chart() □ Chart instance stored for later updates/destroy ``` --- ## Form Best Practices ### Input Validation **WRONG (no validation):** ```html <input type="text" placeholder="Email"> <button>Submit</button> ``` **CORRECT:** ```html <form id="contactForm" novalidate> <label for="email">Email</label> <input type="email" id="email" name="email" required pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$" aria-describedby="email-error" > <span id="email-error" class="error hidden">Please enter a valid email</span> <button type="submit">Submit</button> </form> <script> document.getElementById('contactForm').addEventListener('submit', (e) => { e.preventDefault(); if (e.target.checkValidity()) { // Submit form } else { // Show errors e.target.classList.add('submitted'); } }); </script> ``` ### Form Accessibility Checklist ``` □ Every input has a label with matching for/id □ Required fields marked with required attribute □ Error messages linked with aria-describedby □ Focus states visible (never outline: none without alternative) □ Form has proper role if not using <form> □ Submit button has type="submit" ``` --- ## Modal/Dialog Best Practices ### Function Definition Before Use **WRONG (ReferenceError):** ```html <!-- Function not defined yet! --> <button onclick="openModal('id')">Open</button> <script> // Defined after HTML = might not be available function openModal(id) { /* ... */ } </script> ``` **CORRECT:** ```html <script> // Define before use, or use DOMContentLoaded function openModal(id) { document.getElementById(id).classList.remove('hidden'); } function closeModal(id) { document.getElementById(id).classList.add('hidden'); } </script> <button onclick="openModal('myModal')">Open</button> <div id="myModal" class="hidden"> <button onclick="closeModal('myModal')">Close</button> </div> ``` **Even Better (Event Delegation):** ```html <button data-modal-open="myModal">Open</button> <div id="myModal" class="modal hidden"> <button data-modal-close>Close</button> </div> <script> document.addEventListener('click', (e) => { const openTrigger = e.target.closest('[data-modal-open]'); if (openTrigger) { const modalId = openTrigger.dataset.modalOpen; document.getElementById(modalId).classList.remove('hidden'); } const closeTrigger = e.target.closest('[data-modal-close]'); if (closeTrigger) { closeTrigger.closest('.modal').classList.add('hidden'); } }); </script> ``` --- ## CSS / Tailwind Patterns ### Tailwind Best Practices ```html <!-- Use consistent spacing scale --> <div class="p-4 space-y-2"> <!-- Group related utilities --> <button class=" px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors "> <!-- Extract components for reuse --> <!-- Create components, don't repeat long class strings --> ``` ### Responsive Design ```html <!-- Mobile-first approach --> <div class=" grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 "> ``` ### CSS Variables for Theming ```css :root { --color-primary: #3b82f6; --color-background: #ffffff; } .dark { --color-primary: #60a5fa; --color-background: #1f2937; } ``` --- ## Testing ### Unit Tests (Vitest/Jest) ```typescript describe('calculateTotal', () => { it('should sum all items', () => { const items = [{ price: 10 }, { price: 20 }]; expect(calculateTotal(items)).toBe(30); }); it('should return 0 for empty array', () => { expect(calculateTotal([])).toBe(0); }); }); ``` ### Component Tests (Testing Library) ```typescript import { render, screen, fireEvent } from '@testing-library/react'; test('increments counter on click', async () => { render(<Counter />); const button = screen.getByRole('button'); await fireEvent.click(button); expect(screen.getByText('1')).toBeInTheDocument(); }); ``` ### E2E Tests (Playwright) ```typescript test('user can log in', async ({ page }) => { await page.goto('/login'); await page.fill('[name="email"]', 'user@example.com'); await page.fill('[name="password"]', 'password'); await page.click('button[type="submit"]'); await expect(page).toHaveURL('/dashboard'); }); ``` --- ## Build Tools ### Vite Configuration ```typescript // vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], resolve: { alias: { '@': '/src', }, }, build: { sourcemap: true, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], }, }, }, }, }); ``` ### Package.json Scripts ```json { "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "test": "vitest", "lint": "eslint . --ext .ts,.tsx", "format": "prettier --write ." } } ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/madebyaris/rakitui-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server