| name | nextjs-fullstack-architect |
| description | Advanced Next.js full-stack development skill for building scalable, performant applications with App Router, Server Components, API routes, database integration, authentication, and deployment optimization. |
| author | SellerFin Team |
| license | MIT |
Next.js Full-Stack Architect
Comprehensive skill for architecting and building production-ready Next.js applications with modern patterns, performance optimization, and scalability considerations. Specializes in App Router, Server Components, and full-stack development patterns.
Core Architecture Principles
1. App Router & Server Components
- Server-first architecture design
- Client/Server boundary optimization
- Streaming and Suspense patterns
- Layout and page composition
- Route group organization
- Parallel routes and intercepting routes
2. Data Fetching Strategies
- Server-side data fetching
- Client-side data fetching
- Static generation optimization
- Incremental Static Regeneration (ISR)
- Real-time data with Server-Sent Events
- Optimistic updates
3. Performance Optimization
- Bundle size optimization
- Image optimization strategies
- Font optimization
- Core Web Vitals optimization
- Memory leak prevention
- Lazy loading patterns
4. Security & Authentication
- Authentication patterns
- Authorization strategies
- CSRF protection
- Content Security Policy (CSP)
- Secure headers configuration
- API rate limiting
Project Structure
App Router Organization
app/
├── (auth)/ # Route group for auth pages
│ ├── login/
│ │ └── page.tsx
│ ├── register/
│ │ └── page.tsx
│ └── layout.tsx # Auth-specific layout
├── (dashboard)/ # Route group for authenticated pages
│ ├── dashboard/
│ │ ├── page.tsx
│ │ └── loading.tsx
│ ├── listings/
│ │ ├── page.tsx
│ │ ├── [id]/
│ │ │ ├── page.tsx
│ │ │ ├── edit/
│ │ │ │ └── page.tsx
│ │ │ └── loading.tsx
│ │ └── new/
│ │ └── page.tsx
│ ├── deals/
│ │ ├── page.tsx
│ │ └── [id]/
│ │ ├── page.tsx
│ │ └── @messages/ # Parallel route for messages
│ │ └── page.tsx
│ └── layout.tsx # Dashboard layout with navigation
├── api/ # API routes
│ ├── auth/
│ │ └── route.ts
│ ├── listings/
│ │ ├── route.ts
│ │ └── [id]/
│ │ └── route.ts
│ └── deals/
│ └── route.ts
├── globals.css
├── layout.tsx # Root layout
├── loading.tsx # Global loading UI
├── error.tsx # Global error UI
├── not-found.tsx # 404 page
└── page.tsx # Homepage
Library Organization
lib/
├── auth/
│ ├── config.ts # NextAuth configuration
│ ├── providers.ts # Auth providers
│ └── middleware.ts # Auth middleware
├── database/
│ ├── connection.ts # Database connection
│ ├── models/ # Database models
│ ├── migrations/ # Database migrations
│ └── seeds/ # Database seeds
├── api/
│ ├── client.ts # API client configuration
│ ├── types.ts # API type definitions
│ └── endpoints/ # API endpoint definitions
├── utils/
│ ├── validation.ts # Form validation schemas
│ ├── formatting.ts # Data formatting utilities
│ ├── constants.ts # Application constants
│ └── helpers.ts # Helper functions
├── hooks/
│ ├── use-api.ts # API hooks
│ ├── use-auth.ts # Authentication hooks
│ └── use-local-storage.ts
└── stores/
├── auth-store.ts # Authentication state
├── ui-store.ts # UI state management
└── data-store.ts # Application data state
Server Components Patterns
Data Fetching in Server Components
import { Suspense } from 'react'
import { getListings } from '@/lib/api/listings'
import { ListingCard } from '@/components/listing-card'
import { ListingsSkeleton } from '@/components/skeletons'
interface SearchParams {
industry?: string
priceRange?: string
location?: string
page?: string
}
export default async function ListingsPage({
searchParams,
}: {
searchParams: SearchParams
}) {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">Business Listings</h1>
<Suspense fallback={<ListingsSkeleton />}>
<ListingsContent searchParams={searchParams} />
</>
)
}
() {
listings = ({
: searchParams.,
: searchParams.,
: searchParams.,
: (searchParams.) || ,
:
})
(listings. === ) {
(
)
}
(
)
}
Streaming with Suspense
import { Suspense } from 'react'
import { DashboardMetrics } from '@/components/dashboard-metrics'
import { RecentActivity } from '@/components/recent-activity'
import { DealsPipeline } from '@/components/deals-pipeline'
export default function DashboardPage() {
return (
<div className="space-y-8">
<h1 className="text-3xl font-bold">Dashboard</h1>
{/* Fast-loading metrics */}
<Suspense fallback={<MetricsSkeleton />}>
<DashboardMetrics />
</Suspense>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Independent loading states */}
<Suspense fallback={<ActivitySkeleton />}>
<RecentActivity />
}>
)
}
API Route Patterns
RESTful API Design
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/database'
import { rateLimit } from '@/lib/rate-limit'
const createListingSchema = z.object({
title: z.string().min(1).max(255),
description: z.string().min(10),
askingPrice: z.number().positive(),
industry: z.string(),
revenue: z.number().nonnegative(),
cashFlow: z.number()
})
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
industry = searchParams.()
priceMin = searchParams.()
priceMax = searchParams.()
page = (searchParams.()) ||
limit = .((searchParams.()) || , )
: = {
:
}
(industry) {
where. = industry
}
(priceMin || priceMax) {
where. = {}
(priceMin) where.. = (priceMin)
(priceMax) where.. = (priceMax)
}
[listings, total] = .([
prisma..({
where,
: {
: {
: {
: ,
: ,
:
}
},
: {
: {
: ,
:
}
}
},
: {
:
},
: (page - ) * limit,
: limit
}),
prisma..({ where })
])
.({
listings,
: {
page,
limit,
total,
: .(total / limit)
}
})
} (error) {
.(, error)
.(
{ : },
{ : }
)
}
}
() {
{
(request)
session = ()
(!session?.?.) {
.(
{ : },
{ : }
)
}
body = request.()
validatedData = createListingSchema.(body)
listing = prisma..({
: {
...validatedData,
: session..,
:
},
: {
: {
: {
: ,
:
}
}
}
})
.(listing, { : })
} (error) {
(error z.) {
.(
{ : , : error. },
{ : }
)
}
.(, error)
.(
{ : },
{ : }
)
}
}
Dynamic API Routes
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/database'
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const listing = await prisma.listing.findUnique({
where: { id: params.id },
include: {
seller: {
select: {
firstName: true,
lastName: true,
avatar: true,
createdAt: true
}
},
financials: true,
documents: true,
inquiries: {
include: {
buyer: {
select: {
firstName: ,
:
}
}
}
}
}
})
(!listing) {
.(
{ : },
{ : }
)
}
session = ()
isOwner = session?.?. === listing.
isPublicListing = listing. ===
(!isPublicListing && !isOwner) {
.(
{ : },
{ : }
)
}
(!isOwner) {
(listing ).
(listing ).
(listing ).
}
.(listing)
} (error) {
.(, error)
.(
{ : },
{ : }
)
}
}
() {
{
session = ()
(!session?.?.) {
.(
{ : },
{ : }
)
}
listing = prisma..({
: { : params. },
: { : }
})
(!listing) {
.(
{ : },
{ : }
)
}
(listing. !== session..) {
.(
{ : },
{ : }
)
}
body = request.()
updatedListing = prisma..({
: { : params. },
: {
...body,
: ()
}
})
.(updatedListing)
} (error) {
.(, error)
.(
{ : },
{ : }
)
}
}
Authentication & Authorization
NextAuth.js Configuration
import { NextAuthOptions } from 'next-auth'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import CredentialsProvider from 'next-auth/providers/credentials'
import GoogleProvider from 'next-auth/providers/google'
import { prisma } from '@/lib/database'
import { verify } from 'argon2'
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return
}
user = prisma..({
: { : credentials. }
})
(!user || ! (user., credentials.)) {
}
{
: user.,
: user.,
: user.,
: user.,
: user.
}
}
}),
({
: process..!,
: process..!,
})
],
: {
:
},
: {
: ({ token, user }) => {
(user) {
token. = user.
token. = user.
}
token
},
: ({ session, token }) => {
(token) {
session.. = token.
session.. = token.
}
session
}
},
: {
: ,
: ,
:
}
}
Middleware for Route Protection
import { withAuth } from 'next-auth/middleware'
import { NextResponse } from 'next/server'
export default withAuth(
function middleware(req) {
const token = req.nextauth.token
const { pathname } = req.nextUrl
if (pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', req.url))
}
if (pathname.startsWith('/admin') && token?.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', req.url))
}
if ((pathname === '/login' || pathname === '/register') && token) {
return NextResponse.( (, req.))
}
.()
},
{
: {
: !!token
}
}
)
config = {
: [
,
,
,
]
}
Performance Optimization
Image Optimization
import Image from 'next/image'
import { useState } from 'react'
interface OptimizedImageProps {
src: string
alt: string
width?: number
height?: number
className?: string
priority?: boolean
placeholder?: 'blur' | 'empty'
blurDataURL?: string
}
export function OptimizedImage({
src,
alt,
width = 800,
height = 600,
className,
priority = false,
placeholder = 'blur',
blurDataURL = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=='
}: OptimizedImageProps) {
const [imageError, setImageError] = useState(false)
if (imageError) {
return (
<
=` ${}`}
= , }}
>
Image unavailable
)
}
(
)
}
Bundle Optimization
const nextConfig = {
experimental: {
optimizePackageImports: [
'lucide-react',
'@radix-ui/react-icons',
'@headlessui/react'
],
serverComponentsExternalPackages: ['bcrypt', 'argon2']
},
images: {
domains: ['images.unsplash.com', 's3.amazonaws.com'],
formats: ['image/webp', 'image/avif'],
minimumCacheTTL: 60 * 60 * 24 * 30,
},
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
if (!dev && !isServer) {
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/
},
common: {
name: 'common',
: ,
: ,
:
}
}
}
}
config
},
: ,
: ,
: ,
: {
:
}
}
nextConfig
Database Integration Patterns
Prisma Schema Design
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
firstName String?
lastName String?
role Role @default(BUYER)
emailVerified Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
listings Listing[]
inquiries Inquiry[]
savedListings SavedListing[]
dealRooms DealRoom[]
@@map("users")
}
model Listing {
id String @id @default(cuid())
title String
description String
askingPrice Decimal @db.Decimal(15, 2)
revenue Decimal @db.Decimal(15, 2)
cashFlow Decimal @db.Decimal(15, 2)
industry String
status ListingStatus @default(DRAFT)
sellerId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
seller User @relation(fields: [sellerId], references: [id])
financials Financial[]
documents Document[]
inquiries Inquiry[]
savedBy SavedListing[]
dealRooms DealRoom[]
@@map("listings")
}
enum Role {
BUYER
SELLER
ADMIN
}
enum ListingStatus {
DRAFT
ACTIVE
PENDING
SOLD
ARCHIVED
}
Database Service Layer
import { prisma } from './connection'
import { Prisma } from '@prisma/client'
export class ListingService {
static async getListings(filters: {
industry?: string
priceMin?: number
priceMax?: number
location?: string
page?: number
limit?: number
}) {
const {
industry,
priceMin,
priceMax,
location,
page = 1,
limit = 20
} = filters
const where: Prisma.ListingWhereInput = {
status: 'ACTIVE'
}
if (industry) {
where.industry = industry
}
if (priceMin !== undefined || priceMax !== undefined) {
where.askingPrice = {}
if (priceMin !== undefined) where.askingPrice.gte = priceMin
if (priceMax !== undefined) where.askingPrice.lte = priceMax
}
if (location) {
where.OR = [
{ city: { : location, : } },
{ : { : location, : } }
]
}
[listings, total] = .([
prisma..({
where,
: {
: {
: {
: ,
:
}
},
: {
: {
: ,
:
}
}
},
: {
:
},
: (page - ) * limit,
: limit
}),
prisma..({ where })
])
{
listings,
: {
page,
limit,
total,
: .(total / limit),
: page < .(total / limit),
: page >
}
}
}
() {
listing = prisma..({
: { id },
: {
: {
: {
: ,
: ,
:
}
},
: ,
: userId ? : ,
: {
: {
: ,
:
}
}
}
})
(!listing) {
()
}
canViewSensitiveData = userId === listing.
(!canViewSensitiveData) {
(listing ).
(listing ).
}
listing
}
() {
prisma..({
data,
: {
: {
: {
: ,
:
}
}
}
})
}
() {
listing = prisma..({
: { id },
: { : }
})
(!listing || listing. !== userId) {
()
}
prisma..({
: { id },
: {
...data,
: ()
}
})
}
}
This Next.js Full-Stack Architect skill provides comprehensive patterns for building scalable, performant, and maintainable Next.js applications with modern best practices.