This document describes the high-level architecture of the DevBcn website. If you want to familiarize yourself with the codebase, this is a good place to start.
A Next.js 16 App Router application serving the DevBcn conference website.
Static and multi-year: every past and current edition lives under /{year}.
Speaker, talk, and schedule data come from the Sessionize API at runtime.
Edition-specific configuration (dates, feature flags, sponsors) is defined in code.
Deployed to Vercel with PWA support via @ducanh2912/next-pwa.
Sessionize API ──► hooks/ ──► app/[year]/ pages
▲
config/editions/ ─────────────────┘
Next.js App Router routes. Two routing layers:
- Top-level (
app/(global)/,app/about/,app/blog/, …): year-independent pages. app/[year]/: year-scoped pages (speakers, talks, schedule, sponsors, etc.). Contains its ownlayout.tsxthat resolves the edition config from the URL param.
The app/[year]/@modal/ directory uses Next.js parallel routes for modal intercepts.
app/layout.tsx is the root layout: fonts, global CSS imports, analytics, PWA meta.
Static, compile-time configuration. Three sub-modules:
config/editions/: Per-year config files (2023.ts…2026.ts) implementingEditionConfig. Controls feature flags, dates, sponsor data, Sessionize URLs.CURRENT_EDITIONconstant determines the default year.config/navigation/: Centralized nav links consumed by header and mobile menu.config/job-offers/: Job listing data.
Constraint: config/ must remain pure data — no React, no side effects, no fetching.
Server-side data-fetching functions (despite the directory name, these are not
React hooks). Each wraps a Sessionize API endpoint with react.cache():
useSpeakers.ts→/view/SpeakersuseTalks.ts→/view/SessionsuseSchedule.ts→/view/GridSmart
Types for Sessionize responses live in hooks/types.ts.
Constraint: these functions are designed for Server Components only. They use
fetch with next: { revalidate: 3600 } — do not call them from Client Components.
React components, organized by purpose:
layout/: Shell components — header, footer, mobile menu, breadcrumb, speaker/talk cards, filter bars. Most are Client Components ('use client').elements/: Small interactive widgets — countdown, back-to-top, theme switch, video player, track badges.sections/: Full page sections composed for home page variants (home1/…home10/), FAQ, and venue.ui/: Generic UI primitives (Modal).speakers/,talks/,schedule/,slider/,skeletons/: Domain-specific component groups.
Constraint: components must not import from app/. Data flows down from route
pages through props.
Client-side React Context. Currently only ScheduleContext — manages saved sessions
via localStorage. Provided by ClientLayout at the root.
Constraint: contexts are always Client Components. Keep them minimal to avoid unnecessary re-renders.
Pure utility functions: markdown rendering, JSON-LD generation, analytics helpers, slugification, speaker/talk filter logic.
Constraint: no React imports, no state, no side effects. Must be importable from both server and client code.
SCSS-based styling system:
main.scss: entry point.theme/: design tokens and variables.components/: component-specific styles.layout/: structural layout styles.utils/: mixins and helpers.vendor/: third-party CSS (Bootstrap, AOS, Slick, etc.).
Constraint: no CSS Modules, no Tailwind. All styles flow through the SCSS pipeline.
Shared TypeScript interfaces for component props (HeaderProps, BreadcrumbProps,
BackToTopProps). Sessionize domain types live in hooks/types.ts instead.
Static team member data (TeamMembers.ts).
Jest unit tests mirroring the source tree structure.
Cypress E2E tests live in cypress/.
The [year] dynamic segment is the backbone of the site. Each year's page resolves
its EditionConfig via getEditionConfig(year) and passes it (or derived data)
down through props. Navigation links are prefixed with the active year.
All speaker, talk, and schedule data comes from Sessionize. The API URL is stored
per-edition in config/editions/. Data is cached at the fetch level
(revalidate: 3600) and at the React level (cache()).
- ESLint (flat config) + Prettier + Stylelint enforced via Husky pre-commit hooks.
- lint-staged runs formatters and linters on staged files.
- Git hooks must never be bypassed.
config/is pure data — no React, no fetching, no side effects.hooks/fetch functions are server-only — never call from Client Components.- Components never import from
app/— data flows down via props. - Styles use SCSS exclusively — no CSS Modules, no Tailwind.
- Every edition must implement the full
EditionConfiginterface. - Git hooks (pre-commit, commit-msg, pre-push) are never bypassed.
components/never depends onapp/.hooks/never depends oncomponents/orapp/.config/never depends on any other workspace module.styles/never depends on any other workspace module.lib/never depends onapp/orcomponents/.