Overview
The OWASP Nest frontend is built with Next.js 16, React 19, and TypeScript, using the App Router for server-side rendering and modern React features.
Tech Stack
Next.js 16
React framework with App Router
TypeScript
Type-safe JavaScript
Tailwind CSS 4
Utility-first CSS
Apollo Client
GraphQL client
HeroUI
React component library
Project Structure
frontend/
├── __tests__/ # Test files
│ ├── a11y/ # Accessibility tests
│ ├── e2e/ # End-to-end tests
│ ├── unit/ # Unit tests
│ └── mockData/ # Test fixtures
├── public/ # Static assets
├── src/
│ ├── app/ # Next.js 16 App Router
│ │ ├── about/ # About pages
│ │ ├── api/ # API routes
│ │ ├── auth/ # Authentication pages
│ │ ├── chapters/ # Chapter pages
│ │ ├── committees/ # Committee pages
│ │ ├── community/ # Community pages
│ │ ├── projects/ # Project pages
│ │ ├── layout.tsx # Root layout
│ │ └── page.tsx # Home page
│ ├── components/ # React components
│ │ ├── ui/ # Reusable UI components
│ │ ├── icons/ # Icon components
│ │ └── ... # Feature components
│ ├── contexts/ # React contexts
│ ├── hooks/ # Custom React hooks
│ ├── server/ # Server-side utilities
│ ├── types/ # TypeScript types
│ ├── utils/ # Client utilities
│ └── wrappers/ # Component wrappers
├── .env # Environment variables
├── jest.config.ts # Jest configuration
├── next.config.js # Next.js configuration
├── package.json # Dependencies
├── playwright.config.ts # Playwright configuration
├── tailwind.config.js # Tailwind configuration
└── tsconfig.json # TypeScript configuration
Development Setup
Prerequisites
Ensure you have completed the local development setup first.
Dependencies
Dependencies are managed with pnpm and defined in package.json:
Core
UI
Data Fetching
Dev Tools
{
"dependencies": {
"next": "^16.1.6",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"typescript": "~5.9.3"
}
}
{
"dependencies": {
"@heroui/react": "^2.8.9",
"tailwindcss": "^4.2.1",
"framer-motion": "^12.34.3",
"clsx": "^2.1.1",
"tailwind-merge": "^3.5.0"
}
}
{
"dependencies": {
"@apollo/client": "^4.1.6",
"graphql": "^16.13.0",
"next-auth": "^4.24.13"
}
}
{
"devDependencies": {
"@playwright/test": "^1.58.2",
"jest": "^30.2.0",
"eslint": "^9.39.3",
"prettier": "^3.8.1"
}
}
Update Dependencies
make update-frontend-dependencies
This runs pnpm update to update all frontend dependencies.
Common Commands
Development
Code Quality
GraphQL
Make Commands
# Runs on http://localhost:3000
pnpm run dev
# Check for issues
pnpm run lint:check
# Fix issues
pnpm run lint
This generates TypeScript types from GraphQL schema and queries.
Next.js App Router
File-Based Routing
Next.js uses file-based routing in the app/ directory:
app/
├── page.tsx # / (home)
├── layout.tsx # Root layout
├── about/
│ └── page.tsx # /about
├── projects/
│ ├── page.tsx # /projects
│ ├── [key]/
│ │ └── page.tsx # /projects/[key]
│ └── dashboard/
│ └── page.tsx # /projects/dashboard
└── api/
└── auth/
└── [...nextauth]/
└── route.ts # /api/auth/*
Layouts
Root Layout
Nested Layout
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
</html>
)
}
export default function ProjectsLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="container mx-auto">
<nav>{/* Project navigation */}</nav>
{children}
</div>
)
}
Server vs Client Components
Server Component
Client Component
// No "use client" directive = Server Component
import { getProjects } from '@/server/projects'
export default async function ProjectsPage() {
const projects = await getProjects()
return (
<div>
<h1>Projects</h1>
{projects.map((project) => (
<div key={project.id}>{project.name}</div>
))}
</div>
)
}
Benefits:
- Zero client-side JavaScript
- SEO-friendly
- Direct database/API access
'use client'
import { useState } from 'react'
export function SearchBar() {
const [query, setQuery] = useState('')
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
)
}
When to use:
- Interactive features
- useState, useEffect hooks
- Browser APIs
- Event handlers
Data Fetching
GraphQL with Apollo Client
Define Query
app/projects/queries.graphql
query GetProjects($first: Int) {
projects(first: $first) {
edges {
node {
id
name
description
url
}
}
}
}
Generate Types
Creates queries.generated.ts with TypeScript types. Use in Component
app/projects/ProjectList.tsx
'use client'
import { useQuery } from '@apollo/client'
import { GetProjectsDocument } from './queries.generated'
export function ProjectList() {
const { data, loading, error } = useQuery(GetProjectsDocument, {
variables: { first: 10 },
})
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{data?.projects?.edges?.map(({ node }) => (
<div key={node.id}>{node.name}</div>
))}
</div>
)
}
REST API Fetch
Server Component
Client Component
async function getProjects() {
const res = await fetch('http://backend:8000/api/v0/projects/', {
cache: 'no-store', // or 'force-cache'
})
return res.json()
}
export default async function ProjectsPage() {
const { projects } = await getProjects()
return <div>{/* Render projects */}</div>
}
components/ProjectList.tsx
'use client'
import { useEffect, useState } from 'react'
export function ProjectList() {
const [projects, setProjects] = useState([])
useEffect(() => {
fetch('/api/v0/projects/')
.then((res) => res.json())
.then((data) => setProjects(data.projects))
}, [])
return <div>{/* Render projects */}</div>
}
Styling
Tailwind CSS
Utility Classes
Custom Classes
clsx & tw-merge
<div className="container mx-auto px-4">
<h1 className="text-4xl font-bold text-gray-900 dark:text-white">
OWASP Nest
</h1>
<p className="mt-4 text-lg text-gray-600 dark:text-gray-400">
Your gateway to OWASP
</p>
</div>
module.exports = {
theme: {
extend: {
colors: {
owasp: {
blue: '#1E40AF',
orange: '#F97316',
},
},
},
},
}
Usage:<div className="bg-owasp-blue text-white">
OWASP Blue
</div>
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
function cn(...classes: string[]) {
return twMerge(clsx(classes))
}
// Usage
<div className={cn(
'px-4 py-2',
isActive && 'bg-blue-500',
'hover:bg-blue-600'
)}>
Button
</div>
HeroUI Components
import { Button, Card, CardBody, CardHeader } from '@heroui/react'
export function ProjectCard({ project }) {
return (
<Card>
<CardHeader>
<h3>{project.name}</h3>
</CardHeader>
<CardBody>
<p>{project.description}</p>
<Button color="primary">View Project</Button>
</CardBody>
</Card>
)
}
Authentication
NextAuth.js Setup
Configure Provider
app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GithubProvider from 'next-auth/providers/github'
const handler = NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
],
callbacks: {
async session({ session, token }) {
// Add custom session data
return session
},
},
})
export { handler as GET, handler as POST }
Session Provider
import { SessionProvider } from 'next-auth/react'
export default function RootLayout({ children }) {
return (
<html>
<body>
<SessionProvider>
{children}
</SessionProvider>
</body>
</html>
)
}
Use in Components
'use client'
import { useSession, signIn, signOut } from 'next-auth/react'
export function UserMenu() {
const { data: session } = useSession()
if (!session) {
return <button onClick={() => signIn('github')}>Sign In</button>
}
return (
<div>
<p>Welcome, {session.user?.name}</p>
<button onClick={() => signOut()}>Sign Out</button>
</div>
)
}
Custom Hooks
import { useQuery } from '@apollo/client'
import { GetProjectsDocument } from '@/app/projects/queries.generated'
export function useProjects(limit = 10) {
const { data, loading, error } = useQuery(GetProjectsDocument, {
variables: { first: limit },
})
return {
projects: data?.projects?.edges?.map(({ node }) => node) || [],
loading,
error,
}
}
Usage:
const { projects, loading } = useProjects()
TypeScript
Type Definitions
export interface Project {
id: string
name: string
description: string
level: 'Lab' | 'Production' | 'Flagship'
type: 'Code' | 'Documentation' | 'Tool'
url: string
leaders: string[]
}
GraphQL Generated Types
Generated from GraphQL schema:
Produces:
export type Project = {
__typename?: 'ProjectNode'
id: Scalars['ID']
name: Scalars['String']
description: Scalars['String']
// ...
}
Testing
See the Testing Guide for comprehensive frontend testing documentation.
Environment Variables
Key variables in frontend/.env:
# API URLs
NEXT_PUBLIC_API_URL=http://localhost:8000/
NEXT_PUBLIC_GRAPHQL_URL=http://localhost:8000/graphql/
NEXT_PUBLIC_CSRF_URL=http://localhost:8000/csrf/
# Environment
NEXT_PUBLIC_ENVIRONMENT=development
NEXT_PUBLIC_RELEASE_VERSION=1.0.0
# Auth
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=<secret>
GITHUB_ID=<github-oauth-id>
GITHUB_SECRET=<github-oauth-secret>
# Analytics (optional)
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
NEXT_PUBLIC_SENTRY_DSN=<sentry-dsn>
# Search (optional)
NEXT_PUBLIC_IDX_URL=http://localhost:8000/idx/
Variables prefixed with NEXT_PUBLIC_ are exposed to the browser.
Image Optimization
import Image from 'next/image'
<Image
src="/logo.png"
alt="OWASP Nest"
width={200}
height={50}
priority // Load immediately
/>
Code Splitting
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(() => import('@/components/HeavyComponent'), {
loading: () => <div>Loading...</div>,
ssr: false, // Client-side only
})
Memoization
import { memo, useMemo } from 'react'
export const ProjectCard = memo(({ project }) => {
const formattedDate = useMemo(
() => new Date(project.createdAt).toLocaleDateString(),
[project.createdAt]
)
return <div>{formattedDate}</div>
})
Debugging
Install the React Developer Tools browser extension.
Next.js Debug Mode
NODE_OPTIONS='--inspect' pnpm run dev
Then attach debugger in Chrome DevTools or VS Code.
Console Logging
console.log('Debug:', data)
console.error('Error:', error)
Use Sentry for production errors:import * as Sentry from '@sentry/nextjs'
Sentry.captureException(error)
Build & Deployment
Production Build
Build
Creates optimized production build in .next/ directory. Analyze Bundle
ANALYZE=true pnpm run build
Opens bundle analyzer to identify large dependencies.
Docker Build
make build-frontend-local-image
Builds production Docker image for deployment.
Next Steps
Testing Guide
Write and run frontend tests
Backend Development
Learn about Django backend
Components
Browse UI components
Contributing
Contribution guidelines