Quality checks, patterns, and guidelines for maintaining clean architecture
Maintaining a clean, scalable architecture requires consistent quality checks and adherence to proven patterns. This guide provides real quality checks extracted from the Scope Rule Architects agents.
// ❌ Missing server-only, no error handlingexport async function createProduct(data: ProductData) { await db.products.create(data);}
Route Organization
Proper use of route groups and private folders
Using route groups (group) for organization without URL impact
Using private folders _folder to exclude from routing
Co-locating route-specific code with _components, _hooks
Shared code only in src/shared/ for 2+ route groups
Good structure:
app/ (shop)/ # Route group shop/ page.tsx # /shop route _components/ # Private: only for this route _components/ # Private: shared in shop feature _actions/ # Private: shop server actions
Bad structure:
app/ shop/ components/ # ❌ Not private, affects routing actions/ # ❌ Not private
Performance Optimization
Optimize Core Web Vitals and bundle size
Using next/image for all images
Implementing proper loading states
Using Suspense boundaries strategically
Dynamic imports for heavy components
Metadata configured for SEO
Good:
import Image from 'next/image';import { Suspense } from 'react';export default function ProductPage() { return ( <Suspense fallback={<ProductSkeleton />}> <ProductDetails /> </Suspense> );}
client:idle - Non-critical (newsletter, social widgets)
client:visible - Below fold (comments, related items)
client:media - Responsive (mobile menu)
No directive - Static content
Decision framework:
<!-- Critical: user can't proceed without it --><CheckoutForm client:load /><!-- Important but not critical --><Newsletter client:idle /><!-- Below the fold --><CommentSection client:visible /><!-- Only on mobile --><MobileMenu client:media="(max-width: 768px)" /><!-- Static: no interaction needed --><ProductCard product={product} />
Content Collections
Properly structured and typed content
Content collections defined in src/content/config.ts
Schemas use Zod for validation
Frontmatter is type-safe
Using getCollection() to fetch content
Good:
// src/content/config.tsimport { defineCollection, z } from 'astro:content';const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), publishDate: z.date(), tags: z.array(z.string()), draft: z.boolean().default(false), }),});export const collections = { blog };
---import { getCollection } from 'astro:content';const posts = await getCollection('blog', ({ data }) => { return !data.draft; // Filter drafts});---
Framework Choice
Use the right framework for each island
React for complex state management
Vue for familiar template syntax
Svelte for minimal bundle size
Solid for reactive performance
Consistency within feature boundaries
You can mix frameworks:
<!-- React for complex form --><ContactForm client:load /> {/* React */}<!-- Svelte for lightweight toggle --><ThemeToggle client:idle /> {/* Svelte */}<!-- Vue for familiar syntax --><ProductFilter client:visible /> {/* Vue */}
// ✅ Good: Feature imports from sharedimport { Button } from '@shared/components/ui/button';import { useLocalStorage } from '@shared/hooks/use-local-storage';// ❌ Bad: Features importing from each otherimport { ShopUtil } from '@features/shop/utils';// Auth feature should not depend on Shop feature
❌ Anti-pattern:utils/ helpers.ts # What does this help with? utils.ts # Redundant with folder name misc.ts # Meaningless✅ Better:features/shop/ utils/ price-formatter.ts # Clear purpose inventory-helpers.ts # Clear purposeshared/utils/ date-formatter.ts # Used by 2+ features
❌ Anti-pattern:// Creating shared component before it's used by 2+ featuresshared/ components/ product-card.tsx # Only used in shop (for now)✅ Better:// Keep it local until second feature needs itfeatures/shop/ components/ product-card.tsx # Used only here
// .eslintrc.jsmodule.exports = { rules: { // Prevent features from importing each other 'no-restricted-imports': [ 'error', { patterns: [ { group: ['@features/*/.*'], message: 'Features should not import from other features. Use shared/ instead.', }, ], }, ], },};