SOLID principles for Next.js 16 with modular architecture. Files < 100 lines, interfaces separated, JSDoc mandatory.
/plugin marketplace add fusengine/claude-code-plugins/plugin install fuse:nextjs@fusengine-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Today: January 2026 - ALWAYS use the current year for your searches. Search with "2025" or "2026", NEVER with past years.
CRITICAL: Check today's date first, then search documentation and web BEFORE writing any code.
WORKFLOW:
1. Check date → 2. Research docs + web (current year) → 3. Apply latest patterns → 4. Code
Search queries (replace YYYY with current year):
Next.js [feature] YYYY best practicesReact 19 [component] YYYYTypeScript [pattern] YYYYPrisma 7 [feature] YYYYNever assume - always verify current APIs and patterns exist for the current year.
Before ANY implementation:
Continue implementation by:
Before writing ANY new code:
modules/cores/lib/, modules/cores/components/When creating new code:
modules/cores/lib/modules/cores/components/src/
├── app/ # Routes (orchestration ONLY)
│ ├── (auth)/login/page.tsx # Imports from modules/auth
│ └── layout.tsx
│
└── modules/ # ALL modules here
├── cores/ # Shared (global to app)
│ ├── components/ # Shared UI (Button, Modal)
│ ├── database/ # Prisma client
│ ├── lib/ # Utilities
│ ├── middleware/ # Next.js middlewares
│ └── stores/ # Global state
│
├── auth/ # Feature module
│ ├── api/
│ ├── components/
│ └── src/
│ ├── interfaces/
│ ├── services/
│ ├── hooks/
│ └── stores/
│
└── [feature]/ # Other feature modules
/**
* Fetch user by ID from database.
*
* @param id - User unique identifier
* @returns User object or null if not found
* @throws DatabaseError on connection failure
*/
export async function getUserById(id: string): Promise<User | null>
modules/auth/src/
├── interfaces/ # Types ONLY
│ ├── user.interface.ts
│ └── session.interface.ts
├── services/ # NO types here
└── components/ # NO types here
modules/[feature]/
├── api/ # API routes
├── components/ # UI (< 100 lines each)
└── src/
├── interfaces/ # Types ONLY
├── services/ # Business logic
├── hooks/ # React hooks
└── stores/ # State
modules/cores/
├── components/ # Shared UI (Button, Modal)
├── database/ # Prisma singleton
│ └── prisma.ts
├── lib/ # Utilities
├── middleware/ # Auth, rate limiting
└── stores/ # Global state
// app/(auth)/login/page.tsx
import { LoginForm } from '@/modules/auth/components/LoginForm'
export default function LoginPage() {
return <LoginForm />
}
// modules/auth/src/services/user.service.ts
import { prisma } from '@/modules/cores/database/prisma'
import { hashPassword } from '@/modules/cores/lib/crypto'
// modules/auth/components/LoginForm.tsx
import type { LoginFormProps } from '../src/interfaces/form.interface'
import { useAuth } from '../src/hooks/useAuth'
1 module = 1 feature domain
// ❌ BAD - Mixed concerns in page
export default function LoginPage() {
// validation, API calls, formatting, rendering...
}
// ✅ GOOD - Page orchestrates modules
import { LoginForm } from '@/modules/auth/components/LoginForm'
export default function LoginPage() {
return <LoginForm />
}
Modules extensible without modification
// modules/auth/src/interfaces/provider.interface.ts
export interface AuthProvider {
login(credentials: Credentials): Promise<Session>
logout(): Promise<void>
}
// New providers without modifying existing code
// modules/auth/src/services/github.provider.ts
// modules/auth/src/services/google.provider.ts
All implementations respect contracts
// Any AuthProvider can be swapped
const provider: AuthProvider = new GitHubProvider()
// or
const provider: AuthProvider = new GoogleProvider()
Small, focused interfaces
// ❌ BAD
interface UserModule {
login(): void
logout(): void
updateProfile(): void
sendEmail(): void
}
// ✅ GOOD - Separated
interface Authenticatable { login(): void; logout(): void }
interface Editable { updateProfile(): void }
Depend on interfaces, not implementations
// modules/auth/src/services/auth.service.ts
import type { AuthProvider } from '../interfaces/provider.interface'
export function createAuthService(provider: AuthProvider) {
return {
async authenticate(credentials: Credentials) {
return provider.login(credentials)
}
}
}
// app/(dashboard)/users/page.tsx
import { UserList } from '@/modules/users/components/UserList'
import { getUsers } from '@/modules/users/src/services/user.service'
/**
* Users list page - Server Component.
*/
export default async function UsersPage() {
const users = await getUsers()
return <UserList users={users} />
}
// modules/auth/components/LoginForm.tsx
'use client'
import { useState } from 'react'
import type { LoginFormProps } from '../src/interfaces/form.interface'
/**
* Login form - Client Component.
*/
export function LoginForm({ onSuccess }: LoginFormProps) {
const [email, setEmail] = useState('')
return (
<form onSubmit={() => onSuccess()}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button>Login</button>
</form>
)
}
// modules/auth/src/services/auth.service.ts
import { prisma } from '@/modules/cores/database/prisma'
import type { Credentials, Session } from '../interfaces/user.interface'
/**
* Authenticate user with credentials.
*/
export async function authenticate(credentials: Credentials): Promise<Session | null> {
const user = await prisma.user.findUnique({
where: { email: credentials.email }
})
if (!user) return null
return { user, token: '...', expiresAt: new Date() }
}
anyapp/ pages'use client' by defaultuseEffect for data fetchingany type