Developer Guide
Conventions every page and component in this repo follows. Read this before adding a new route or component.
1. Folder Structure
Colocation vs. global — pick based on how many routes need the component. Reach for the tiers below top to bottom: start in a route's own _components/, and only promote a component to components/ once a second route actually needs it.
iskcon-website/
├── next.config.js # Image remotePatterns (S3), Sass include paths
├── package.json
│
├── app/ # App Router — all routing happens here
│ ├── layout.jsx # Root layout: persistent global Header & Footer
│ ├── page.jsx # Homepage: lean orchestrator, composes sections
│ │
│ ├── _components/ # PRIVATE — used only by app/page.jsx
│ │ └── homepage/
│ │ ├── HomeBanner.jsx # One file per homepage section
│ │ ├── AboutSection.jsx
│ │ └── ... # 12 sections total, same pattern
│ │
│ └── products/ # Dynamic product routes
│ ├── page.jsx # Product catalog page
│ ├── _components/
│ │ └── ProductCatalog.jsx
│ └── [id]/
│ ├── page.jsx # Product detail page
│ └── _components/
│ └── ProductDetail.jsx
│
├── components/ # GLOBAL — reusable across 2+ routes
│ ├── Header.jsx # Renders once, in app/layout.jsx
│ ├── Footer.jsx # Renders once, in app/layout.jsx
│ ├── SectionHeading.jsx # Shared heading block, used by most sections
│ ├── DynamicCTA.jsx
│ ├── ProductCard.jsx # + CourseCard, DonationCard, FestivalCard, ...
│ │
│ └── ui/ # Atomic primitives — no business logic
│ ├── CustomButton.jsx # The button component
│ ├── OptimizedImage.jsx # next/image + the Zero-Blink pipeline
│ └── Skeleton.jsx
│
├── lib/ # Server-side data/logic, shared across routes
│ ├── products.js
│ └── templeData.js
│
└── styles/
├── style.scss # Imports Bootstrap source + every partial below
├── _variables.scss # Theme colors/fonts/spacing — edit here, not ad hoc CSS
├── _mixins.scss # Breakpoint mixins
└── _*.scss # One partial per homepage section (e.g. _join-us.scss)2. Fonts
Three typefaces, each scoped to one role. All are registered once in app/layout.jsx via next/font/google and exposed globally as CSS variables on the <html> element — never load a font anywhere else.
- Montserrat (
var(--font-montserrat)) — the global default, applied to*andbodyinstyles/_init.scss. Every element inherits it unless overridden. - Cinzel (
var(--font-cinzel)) — the small eyebrow.taglabel above section headings. - EB Garamond (
var(--font-eb-garamond)) — the heading font: everyh1/h2/h3inside a.headingblock (rendered viaSectionHeading), and also the footer link column headings.
Importing a new font
Three steps, all in app/layout.jsx — never install a font package or add a <link> tag.
// 1. import + configure
import { Poppins } from "next/font/google";
const poppins = Poppins({
variable: "--font-poppins",
subsets: ["latin"],
weight: ["400", "600"],
});
// 2. add its variable to the <html> className
<html className={`${montserrat.variable} ${poppins.variable} ...`}>
// 3. reference it in any .scss file
h4 {
font-family: var(--font-poppins);
}3. Colors
8 solid colors and 10 gradients, defined once in styles/_variables.scss. Change the site's look by editing values here — never hardcode a hex code in component SCSS.
$white-color#fff$primary-color#a8282f$dark-primary#430609$secondary-color#f3b11e$light-secondary#fdebb4$light-bg#fcf6eb$text-color#6e6559$bold-color#55413eGradients
$light-primary-gradient$dark-primary-gradient$dark2-primary-gradient$dark2-primary-gradient-inverse$light-secondary-gradient$primary-yellow-button$dark-secondary-gradient$bg-gradient-left-right$bg-gradient-top-bottom$text-gradient4. Using OptimizedImage
Every image passes blurDataURL in from a server-side fetch or a precomputed constant — never generated on the client — and picks one of two sizing modes.
aspectRatio (fluid fill mode) — vs. — width/height (fixed mode)
Both are supported by components/ui/OptimizedImage.jsx, but they solve different problems — pick based on whether the image's box is defined by its content or by its container.
- aspectRatio — use for photos where the layout dictates the box (hero banners, cards, carousel slides). The image stretches with
fillto whatever box the ratio class describes (e.g."ratio-16x9","ratio-4x3","ratio-1x1") and is cropped withobjectFit="cover"(the default) to fill it. Always pass a realsizesmatching the rendered width per breakpoint — the browser can't know how big the box will be otherwise, and a wrongsizesmeans an oversized download. - width / height — use for anything with a genuine fixed pixel size that must not stretch or crop: icons, logos, avatars. Pass the real intrinsic (or intended) pixel dimensions. If the icon isn't square, also set
objectFit="contain"(fixed mode itself doesn't crop, but a mismatched box can still squash a non-square source) —sizesis irrelevant here since the box never changes size.
aspectRatio fill mode inside a container that already has its own non-standard CSS aspect ratio (not a plain ratio-Nx N): the wrapper always carries Bootstrap's position-relative utility, which compiles with !important. If your own CSS needs the wrapper to be position: absolute instead (e.g. to fill an already-sized ancestor), your override needs !important too, or it silently loses.Above-the-fold hero banner
Full-bleed, likely the Largest Contentful Paint element: set priority to skip lazy-loading, and sizes="100vw" since it always spans the viewport width.
<OptimizedImage
src={hero.imageUrl}
alt="Devotees gathered for the Janmashtami celebration"
blurDataURL={hero.blurDataURL} // fetched/computed server-side
aspectRatio="ratio-21x9"
sizes="100vw"
priority
className="w-100"
/>Fixed-size icon
No blurDataURL needed for small static art — the blur-in fade only makes sense when there's a real photo underneath.
<OptimizedImage
src={tickIcon}
alt="Tick"
width={19}
height={19}
/>5. CustomButton
components/ui/CustomButton.jsx is the one button component — every button on the site should use it instead of a raw <button> or Bootstrap's btn classes directly.
variant— one ofprimary,secondary,primaryOutline,secondaryOutline,transparent. Anything else passed is used as-is for a one-off style.typedefaults to"button"— settype="submit"explicitly inside forms.- All other props (
onClick,disabled, …) pass straight through to the underlying<button>.
Live sample
<CustomButton variant="primary">Register</CustomButton>
<CustomButton variant="secondary" onClick={handleClick}>
Support the festival
</CustomButton>
<CustomButton type="submit" variant="primary" className="w-100">
Submit
</CustomButton>6. SectionHeading
components/SectionHeading.jsx is the shared heading block for every section — the small eyebrow tag, the <h2>, and the supporting paragraph underneath it.
tag— optional small eyebrow label above the heading (e.g."ABOUT ISKCON TEMPLE").heading— the actual heading element, passed in already wrapped (usually an<h2>) so callers control the tag and any inline<span>styling.description— supporting copy underneath, also passed in already wrapped (<p>or a fragment of several).classes— extra class names merged onto the wrapping.headingdiv for section-specific spacing tweaks.
Live sample
ABOUT ISKCON TEMPLE
Discover the Joy of Krishna Consciousness
Welcome to the temple of Sri Sri Radha Madhava…
<SectionHeading
tag="ABOUT ISKCON TEMPLE"
heading={<h2>Discover the Joy of Krishna Consciousness</h2>}
description={<p>Welcome to the temple of Sri Sri Radha Madhava…</p>}
/>7. Creating a New Section
Every homepage-style section follows the same shell. Copy this, rename the class, and fill in the content — don't invent a new outer structure.
import SectionHeading from "@/components/SectionHeading";
const YourSection = () => {
return (
<section className="your-section-name">
<div className="container">
<SectionHeading
tag="OPTIONAL EYEBROW TAG"
heading={<h2>Your Section Heading</h2>}
description={<p>One or two lines of supporting copy.</p>}
/>
{/* section content goes here */}
</div>
</section>
);
};
export default YourSection;Then add a matching styles/_your-section-name.scss partial scoped under .your-section-name, and add one @import "your-section-name"; line to styles/style.scss — following the same pattern as the existing sections (e.g. _join-us.scss, _upcoming-festival.scss).