Skip to main content
The NJ Rajat Mahotsav platform is a production Next.js 15 application built for the Shree Swaminarayan Temple Secaucus 25th Anniversary celebration (July 29 – August 2, 2026). It handles event registration, admin workflows, media delivery, and community seva submissions.

Global provider tree

The root layout (app/layout.tsx) wraps every page in three nested providers. They must be composed in this order because each layer depends on the one outside it:
app/layout.tsx
<LoadingProvider>
  <AudioProvider>
    <ThemeProvider attribute="class" defaultTheme="light" forcedTheme="light">
      <Navigation />
      <main>{children}</main>
      <StickyFooter />
      <ScrollToTop />
      <FloatingMenuButton />
      <AudioPlayer />
    </ThemeProvider>
  </AudioProvider>
</LoadingProvider>
ProviderSourceResponsibility
LoadingProviderhooks/use-loading.tsxGlobal loading state shared across pages and route transitions
AudioProvidercontexts/audio-context.tsxBackground prayer audio with play/pause, fade-in, fade-out, and user-consent gating
ThemeProvidercomponents/atoms/theme-providerForced to light mode (forcedTheme="light") — dark mode is disabled
Every page also receives the persistent Navigation, StickyFooter, ScrollToTop, FloatingMenuButton, and AudioPlayer components regardless of the current route.

Audio system

The AudioProvider manages a single HTMLAudioElement that streams prayer audio from the Cloudflare R2 CDN. Key behaviors:
  • User-consent gating — the audio element is created with volume = 0 and will not play until grantConsent() is called, satisfying browser autoplay policies.
  • Fade controlsfadeOut(duration) and fadeToVolume(targetVolume, duration) animate volume in 50-step intervals so transitions feel smooth.
  • Persistent across routes — audio continues playing as the user navigates between pages because the provider lives in the root layout.
The context exposes: play, pause, fadeOut, fadeToVolume, toggle, isPlaying, isLoaded, hasUserConsent, and grantConsent.

Routing

The platform uses the Next.js 15 App Router. All routes are defined under app/:
RoutePurpose
/Main landing page — hero, countdown, media galleries, highlights
/registrationMulti-step event registration form with Supabase backend
/scheduleInteractive event timeline with mobile and desktop variants
/community-sevaCommunity service submission form
/spiritual-sevaSpiritual service submission form
/guest-servicesGuest services information page
/admin/registrationsProtected admin dashboard — requires Google OAuth
/auth/callbackSupabase OAuth callback handler — validates domain and redirects

Middleware and session refresh

middleware.ts intercepts every request (except static files and image assets) and calls updateSession from utils/supabase/middleware to keep the Supabase session cookie fresh:
middleware.ts
export const config = {
  matcher: [
    "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
  ],
}

State management

The platform uses React Context API for global state and custom hooks for encapsulated local logic. There is no Redux or Zustand.
HookFilePurpose
useAudioContextcontexts/audio-context.tsxConsume audio state and controls from AudioProvider
useLoadinghooks/use-loading.tsxGlobal loading state for page transitions
useAudiohooks/use-audio.tsLow-level audio playback utilities
useDeviceTypehooks/use-device-type.tsDetect mobile vs. desktop for conditional rendering
useIntersectionObserverhooks/use-intersection-observer.tsScroll-based animation triggers
useToasthooks/use-toast.tsToast notification queue
Form state is managed locally with React Hook Form and validated with Zod schemas. Server data is read directly from Supabase in Server Components or via API routes — no additional caching layer.

Data flow

Browser

  ├── Static assets ──────────────────► Cloudflare R2 CDN
  │                                      https://cdn.njrajatmahotsav.com

  ├── Optimized images ────────────────► Cloudflare Images
  │                                      https://imagedelivery.net/...

  ├── Form submissions (registration,
  │   seva) ──────────────────────────► Supabase (PostgreSQL + RLS)

  ├── Admin auth ──────────────────────► Supabase Auth (Google OAuth)
  │                                        └── /auth/callback validates domain

  └── File downloads ─────────────────► /api/download (URL-allowlisted proxy)
                                         └── fetches from R2 via AWS SDK

CDN architecture

All assets are served from Cloudflare infrastructure.

Cloudflare R2 (static assets)

Static files — audio, PDFs, and non-optimized images — are stored in R2 and served via a custom domain:
https://cdn.njrajatmahotsav.com/<path>

Cloudflare Images (responsive images)

Photos use Cloudflare Images for on-the-fly resizing and format conversion. Three named variants are defined:
VariantHelper functionUse case
biggergetCloudflareImage(id)Standard responsive images
mobileWPgetCloudflareImageMobileWp(id)Mobile wallpaper / background images
biggestgetCloudflareImageBiggest(id)Full-resolution hero images
All helpers live in lib/cdn-assets.ts and return fully-qualified imagedelivery.net URLs.
Next.js image optimization is disabled (images.unoptimized: true in next.config.mjs) because Cloudflare Images handles all resizing and format conversion externally.

Security model

Security is layered across multiple levels:
  • HTTP headersX-Frame-Options: DENY, X-Content-Type-Options: nosniff, X-XSS-Protection, Referrer-Policy, and Permissions-Policy are set for every route via next.config.mjs.
  • Row Level Security — Supabase RLS policies restrict database access at the row level.
  • Domain-restricted adminlib/admin-auth.ts enforces an ALLOWED_DOMAIN constant; any Google account outside that domain is rejected at /auth/callback.
  • URL allowlisting — the /api/download route validates URLs before proxying requests to R2.
  • No hardcoded secrets — all credentials are read from environment variables.
Before deploying to production, configure Supabase RLS policies for your domain, update ALLOWED_DOMAIN in lib/admin-auth.ts, and add server-side rate limiting. See the README security section for a full checklist.

Build docs developers (and LLMs) love