Hireable LogoHireable
Frontend

Frontend Architecture

Vertical-slice architecture and patterns used in the Hireable frontend application

Overview

The frontend is built with Next.js 16 (App Router) and React 19, following a vertical-slice (feature-first) architecture for scalability and maintainability.

Technology Stack

TechnologyVersionPurpose
Next.js16.0.3React framework with App Router
React19.0.0UI library
TypeScript5.7.2Type safety
Tailwind CSS4.1.17Styling
Radix UILatestHeadless UI primitives
Framer Motion12.23.24Animations
React Hook Form7.67.0Form handling
Zod4.1.13Schema validation

Directory Structure

apps/web/src/
├── app/                      # Next.js App Router (Routing Layer)
├── features/                 # Vertical Slices (Feature-First)
│   ├── auth/                 # Authentication feature
│   ├── waitlist/             # Waitlist feature
│   └── screens/              # Page implementations
├── components/               # Global Shared UI
├── services/                 # Cross-Feature API Clients
├── hooks/                    # Global Custom Hooks
├── providers/                # Global Context Providers
├── lib/                      # Utilities & Helpers
└── types/                    # Shared Types

Vertical-Slice Architecture

Features are organized as self-contained slices under src/features/. Each slice owns its services, types, hooks, and components.

Feature Structure

features/
└── auth/
    ├── components/           # Feature-specific UI
    ├── hooks/                # Feature-specific hooks
    ├── services/             # API calls & mappers
    │   ├── auth.service.ts
    │   └── mappers.ts
    ├── types/                # Feature types
    │   └── index.ts
    └── index.ts              # Public API exports

Public API Pattern

Each feature exports a public API through index.ts:

// features/auth/index.ts
export { authService } from "./services/auth.service";
export type { LoginRequest, LoginResponse } from "./types";

Import Rules

// ✅ Preferred - Import from feature public API
import { authService } from "@/features/auth";
 
// ❌ Avoid - Deep imports into feature internals
import { authService } from "@/features/auth/services/auth.service";

App Router Structure

The app/ directory uses Next.js 16 App Router conventions:

app/
├── (auth)/                   # Route group for auth pages
│   ├── login/page.tsx
│   ├── signup/page.tsx
│   ├── forgot-password/page.tsx
│   └── reset-password/page.tsx
├── api/                      # API routes
├── privacy-policy/page.tsx
├── layout.tsx                # Root layout
├── page.tsx                  # Home page
├── not-found.tsx             # 404 page
├── globals.css               # Global styles
├── manifest.ts               # PWA manifest
├── robots.ts                 # SEO robots.txt
└── sitemap.ts                # SEO sitemap

Route Groups

Route groups (folders with parentheses) organize routes without affecting the URL:

(auth)/login/page.tsx    → /login
(auth)/signup/page.tsx   → /signup

Screens Pattern

Page implementations live in features/screens/:

features/screens/
├── LoginPage/
│   ├── LoginPage.tsx
│   └── index.ts
├── SignupPage/
│   ├── SignupFormPage.tsx
│   ├── SignupRoleSelectionPage.tsx
│   └── index.ts
└── WaitlistPage/
    ├── WaitlistPage.tsx
    ├── hooks/
    ├── modals/
    ├── sections/
    └── index.ts

The App Router pages import from screens:

// app/(auth)/login/page.tsx
import { LoginPage } from "@/features/screens/LoginPage";
 
export default function Page() {
  return <LoginPage />;
}

Data Flow

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Component │ ──▶ │   Service   │ ──▶ │   Backend   │
│   (React)   │     │  (API Call) │     │  (Express)  │
└─────────────┘     └─────────────┘     └─────────────┘
       │                   │                   │
       │                   │                   │
       ▼                   ▼                   ▼
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│    Hook     │     │   Mapper    │     │  Database   │
│  (State)    │     │ (Transform) │     │  (MongoDB)  │
└─────────────┘     └─────────────┘     └─────────────┘
  1. Components call hooks or services
  2. Services make HTTP requests to the backend
  3. Mappers transform API responses to domain models
  4. Hooks manage local state and side effects

Adding New Features

1. Create Feature Slice

mkdir -p src/features/jobs/{components,hooks,services,types}

2. Define Types

// features/jobs/types/index.ts
export interface Job {
  id: string;
  title: string;
  description: string;
  // ...
}

3. Create Service

// features/jobs/services/jobs.service.ts
import { apiClient } from "@/services/api";
 
export const jobsService = {
  async getAll() {
    const response = await apiClient.get("/jobs");
    return response.data;
  },
  // ...
};

4. Export Public API

// features/jobs/index.ts
export { jobsService } from "./services/jobs.service";
export type { Job } from "./types";

5. Create Screen

// features/screens/JobsPage/JobsPage.tsx
import { jobsService } from "@/features/jobs";
 
export function JobsPage() {
  // Implementation
}

6. Add Route

// app/jobs/page.tsx
import { JobsPage } from "@/features/screens/JobsPage";
 
export default function Page() {
  return <JobsPage />;
}