Next.js Frontend Architecture
Documentation for frontend code organization, styling system, and state management patterns.
🏗️ Frontend Code Organization
App Router Structure
The project uses Next.js 15 App Router with route groups for logical organization:
app/
├── (authenticated)/ # Protected routes requiring auth
│ ├── (user-app)/ # User-facing application
│ └── (dev-app)/ # Developer/admin interface
├── (public-docs)/ # Public documentation
├── (public-with-convex-auth-client)/ # Auth pages
└── (public-with-convex-client)/ # Public pages with Convex
Page-Level Organization
Each route follows a consistent pattern for organizing components and logic:
w/[workspace-slug]/
├── page.tsx # Route component
├── layout.tsx # Layout wrapper
├── _components/ # Page-specific components
│ ├── AppSidebar/
│ │ ├── index.tsx
│ │ ├── AppSidebarHeader.tsx
│ │ └── AppSidebarFooter.tsx
│ ├── AppTopBar.tsx
│ └── InviteMembersDialog.tsx
└── _hooks/ # Page-specific custom hooks
├── useCurrentUser.get.tsx
├── useCurrentWorkspace.get.tsx
└── useWorkspaceMembers.remove.tsx
Custom Hooks Pattern
Custom hooks combine data fetching with UI components for better encapsulation:
// _hooks/useCurrentUser.get.tsx
export default function useGetCurrentUser() {
const { data: _user, isPending } = useConvexQuery(
api.core.users.getCurrentUser,
{ includeDetail: ["picture"] }
);
const UserAvatar = ({ className, fallbackClassName }) => (
<Avatar className={className}>
<AvatarImage src={_user?.profileUrl ?? ""} />
<AvatarFallback className={fallbackClassName}>
{_user?.name?.charAt(0)}
</AvatarFallback>
</Avatar>
);
return {
user: { ..._user, Avatar: UserAvatar },
isPending,
};
}
Key Benefits:
- Encapsulation: Logic and UI components together
- Reusability: Easy to use across different pages
- Type Safety: Full TypeScript integration
- Naming Convention:
use<Feature>.<operation>.tsx
Enhanced Data Fetching
Uses custom useConvexQuery
hook built on convex-helpers
for richer query status:
// hooks/useConvexQuery.tsx
import { makeUseQueryWithStatus } from 'convex-helpers/react';
import { useQueries } from 'convex/react';
const useConvexQuery = makeUseQueryWithStatus(useQueries);
export default useConvexQuery;
Provides enhanced status beyond standard useQuery
:
isPending
- Initial loading stateisLoading
- Any loading stateerror
- Error informationisSuccess
- Success state
🎨 Styling System
Tailwind CSS Setup
Modern Configuration:
- Tailwind CSS 4 with inline theme configuration
- Modern color space:
oklch()
for better color perception - CSS Variables: Dynamic theming support
- Custom variants: Dark mode with
@custom-variant
/* globals.css */
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
/* ... more variables */
}
Theme Management
Two-layer theming system:
- CSS Variables for dynamic colors
- next-themes for system/light/dark switching
// app/layout.tsx
<ThemeProvider
attribute='class'
defaultTheme='system'
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
Features:
- System preference detection
- Seamless transitions disabled for performance
- Class-based theme switching
- Consistent color tokens across components
Component Library
shadcn/ui with extensive customization:
components/ui/
├── accordion.tsx # Collapsible content
├── alert-dialog.tsx # Modal confirmations
├── avatar.tsx # User profile images
├── button.tsx # Primary interaction
├── card.tsx # Content containers
├── command.tsx # Command palette
├── dialog.tsx # Modal dialogs
├── dropdown-menu.tsx # Context menus
├── input.tsx # Form inputs
├── sidebar.tsx # Navigation sidebar
└── ...
Design System Tokens:
- Colors: Primary, secondary, muted, destructive
- Spacing: Consistent margin/padding scale
- Typography: Geist Sans + Geist Mono fonts
- Border Radius:
--radius
system (0.625rem base)
🗄️ Global State Management
Zustand Store Pattern
Organized by feature domains:
// store/dialog.store.ts
interface DialogStore {
todos: {
create: DialogControls;
};
command: DialogControls;
// More dialog groups...
}
export const useDialogStore = create<DialogStore>(set => ({
todos: {
create: {
isOpen: false,
withToast: false,
onOpen: options => set(/* ... */),
onClose: () => set(/* ... */),
},
},
command: {
isOpen: false,
onOpen: () => set(/* ... */),
onClose: () => set(/* ... */),
},
}));
Usage Pattern:
// In components
const { isOpen, onOpen, onClose } = useDialogStore(state => state.todos.create);
Provider Architecture
Layered provider setup:
// app/layout.tsx
<ConvexAuthNextjsServerProvider>
<html lang='en' suppressHydrationWarning>
<body>
<ThemeProvider>{children}</ThemeProvider>
<Toaster />
</body>
</html>
</ConvexAuthNextjsServerProvider>
Two Convex providers for different needs:
// ConvexClientProvider.tsx
// For authenticated queries
export function ConvexAuthClientProvider({ children }) {
return (
<ConvexAuthNextjsProvider client={convex}>
{children}
</ConvexAuthNextjsProvider>
);
}
// For public queries
export function ConvexClientProvider({ children }) {
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
🛠️ Development Patterns
Component Organization
Hierarchical structure:
- Shared components:
/components/
directory - Page-specific:
_components/
folders - Complex components: Subfolder with
index.tsx
- UI primitives:
components/ui/
from shadcn
File Naming Conventions
- Components:
PascalCase.tsx
- Hooks:
use<Feature>.<operation>.tsx
- Pages:
page.tsx
,layout.tsx
,loading.tsx
- Route groups:
(group-name)/
for logical organization
Import Organization
Consistent import order:
- React & external libraries
- Convex imports
- Internal components
- Custom hooks
- Types & validators
- Relative imports
// External
import React from 'react';
import { Button } from '@/components/ui/button';
// Convex
import { api } from '@/convex/_generated/api';
// Internal
import useCurrentUser from './_hooks/useCurrentUser.get';
// Types
import type { WorkspaceId } from '@/convex/_generated/dataModel';
🔄 Key Benefits
Scalable Architecture:
- Route groups prevent URL pollution
- Page-specific organization reduces coupling
- Custom hooks encapsulate complex logic
Developer Experience:
- Consistent file organization
- Type-safe throughout
- Hot reload optimization
- Path aliases for clean imports
Performance:
- Server components by default
- Optimized bundle splitting
- Efficient re-renders with proper state isolation