Freeframe

Documentation

Gather

v1.0.2Updated Oct 16, 2026A warm, modern church website with a live service countdown, sermon player, events calendar and visit planner.

Overview#

Gather is a website for a local church, built for Riverbend Church, a fictional non-denominational church in Lawrence, Kansas. It has a home page with this Sunday's times and a countdown to the next service, a plan-your-visit page, a sermon archive and sermon pages with an audio player, an events calendar with calendar downloads, a small-group finder and ministries, a giving page, an about page with the staff team, contact and prayer requests, and a 404 page. This is the Next.js version: Next.js 16 (App Router, static export), React 19, TypeScript and Tailwind CSS v4. Sermons, events, groups, giving and staff live in typed data files in src/data/. This guide covers version 1.0.0. See it live at freeframe.dev/templates/gather.

Quick start#

Requires Node.js 24 and pnpm.

sh
pnpm install
pnpm dev          # http://localhost:3000

pnpm build writes the static site to out/, and pnpm start serves it. pnpm lint and pnpm typecheck check the code.

Pages#

Route File Purpose and notable features
/ src/app/page.tsx Hero with river illustration, "This Sunday" card with times and the next-service countdown, three-step "I'm new" path, latest sermon with player, next four events (with the event dialog), ministries, scripture and figures band, newsletter sign-up
/visit src/app/visit/page.tsx What to expect, service times, kids' check-in steps and rooms, accessibility, SVG parking map, FAQ accordion, plan-your-visit form with live summary
/sermons src/app/sermons/page.tsx "Watch live" panel with countdown and the latest message, series cards, search and series/speaker/book filters with URL state, sermon cards
/sermon src/app/sermon/page.tsx One sermon (/sermon?id=room-at-the-table): series cover, audio player, key verse, notes, discussion questions with copy button, speaker card, the rest of the series, related sermons
/events src/app/events/page.tsx Category filter, month calendar and list views, selected-day agenda, event dialog with "Add to calendar" (.ics)
/groups src/app/groups/page.tsx Small-group finder (day, area, season of life, childcare), ministries for kids, youth, care and music (#kids, #youth, #care, #music)
/give src/app/give/page.tsx Frequency, amount chips and custom amount, fund, cover-the-fee option, live total, budget bar, funds, FAQ
/about src/app/about/page.tsx Story timeline, beliefs (#beliefs), leadership, staff grid with SVG portraits
/contact src/app/contact/page.tsx Office details, urgent pastoral contact, contact form, prayer request form (#prayer)
404 src/app/not-found.tsx Signpost illustration and links back (exported as 404.html)

Project structure#

gather/
  src/app/
    layout.tsx            <html>, skip link, header, <main>, footer, scroll reveal, back to top, default metadata
    fonts.ts              Nunito Sans and Urbanist with next/font/local
    globals.css           Tailwind entry: brand tokens (@theme) and component classes
    icon.svg              favicon (the Riverbend logo)
    page.tsx …            one folder per route (see Pages), not-found.tsx for the 404 page
  src/components/
    SiteHeader.tsx        top bar, sticky header, main and mobile menus
    ServiceStatus.tsx     "Next gathering … / Live now" line in the top bar
    Countdown.tsx         countdown to the next service / "Live now" card
    ThisSunday.tsx        the date of this Sunday
    AudioPlayer.tsx       the sermon player
    SermonArchive.tsx     series cards, filters and sermon list (/sermons)
    SermonDetail.tsx      everything on /sermon
    SermonCard.tsx        a sermon in a grid
    LatestSermon.tsx, LatestPlayer.tsx, HomeEvents.tsx   home and sermons page pieces
    EventsCalendar.tsx    month grid, day agenda and list view (/events)
    EventCard.tsx         an event date as a card button
    EventDialog.tsx       the event detail dialog and .ics download
    GroupFinder.tsx       group filters and cards (/groups)
    forms/                validation.tsx (shared), NewsletterForm, VisitForm, GiveForm, ContactForm, PrayerForm
    art/Avatar.tsx        SVG staff portraits
    art/SeriesArt.tsx     SVG series covers
    WithUrl.tsx           reads the URL for widgets after the page loads
    Icons.tsx, Logo.tsx, SiteFooter.tsx, Year.tsx, Reveal.tsx, BackToTop.tsx
  src/data/
    church.ts             CHURCH, SERVICES, KEEP_DEMO_CURRENT, DEMO_ANCHOR
    people.ts             PEOPLE, STAFF
    sermons.ts            SERIES, BOOKS, SERMONS, PROGRESS_KEY
    events.ts             EVENT_CATS, EVENTS
    groups.ts             DAYS, AREAS, STAGES, GROUPS
    giving.ts             GIVING
    visit.ts              VISIT
    site.ts               NAV, MOBILE_NAV
  src/lib/
    dates.ts              day numbers, formatting and the church's time zone
    demo.ts               calendar(): shifted sermon and event dates, every event occurrence
    services.ts           serviceState(): the next or current service
    ics.ts                buildIcs() and downloadIcs()
    progress.ts           saved listening positions
    url.ts, storage.ts, mounted.ts, tick.ts, metadata.ts, asset.ts
  src/fonts/              the woff2 files and their licences
  public/img/             og-image.png (social image) and og-image.svg (its source)
  next.config.ts, eslint.config.mjs, tsconfig.json, postcss.config.mjs, package.json

Customising#

Brand and colours#

The design tokens are in the @theme block of src/app/globals.css. Every utility such as bg-plum or text-muted comes from them.

Token Default Used for
--color-paper #fbf6ee Page background
--color-shell #fffdf9 Cards and form fields
--color-sand #f4eadb Soft panels, filter bars
--color-line #e5d8c5 Hairlines and card rings
--color-ink #2b1b36 Text, the footer, dark bands and the audio player
--color-muted #5f5468 Secondary text
--color-plum / --color-plum-dark #5b2a6e / #431d52 Brand colour, primary buttons, links
--color-lilac #efe5f5 Selected states, worship events
--color-apricot / -light / -dark #f6a96b / #fde3cc / #9a4716 Warm accent (backgrounds), tint, accent text
--color-sage / -light / -dark #c9dcc0 / #e6efe0 / #33603b Hills, care ministry, serve events, success
--color-river / -light / -dark #bcd9e8 / #e3f0f6 / #1f5577 River, kids' ministry and events
--color-rose #b3364a Errors, "Live now", classes events

To change the brand colour, edit the tokens; the dev server picks the change up at once:

css
@theme {
  --color-plum: #1f5c7a;
  --color-plum-dark: #164459;
}

Use apricot only as a background: for text on light backgrounds use apricot-dark, which passes WCAG AA. The SVG artwork (hero, map, portraits, series covers, logo) uses literal hex values; search src/ for #5b2a6e and #f6a96b to recolour it. Also change themeColor in src/app/layout.tsx if you change the page background.

Component classes in globals.css: .btn-primary, .btn-warm, .btn-outline, .btn-light, .btn-sm, .card, .card-hover, .eyebrow, .h-display, .h-section, .tag, .chip-dot, .link, .field, .field-label, .field-error, .option and .pill (radio and checkbox cards that highlight when checked), .check, .faq (the accordion) and .scrub (the player's slider).

Typography#

Urbanist (headings, font-display) and Nunito Sans (body text, font-sans) are self-hosted with next/font/local in src/app/fonts.ts, from the variable Latin woff2 files in src/fonts/ (the same files Google Fonts serves, SIL Open Font License). They keep their real family names, so globals.css and the SVG text (fontFamily="Urbanist, sans-serif") use them directly:

css
--font-sans: "Nunito Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--font-display: "Urbanist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;

To swap a font, put its woff2 files in src/fonts/, point the src and the font-family declaration in fonts.ts at them, and change the names in @theme. You can also use next/font/google instead.

Content#

Page copy is in the page files in src/app/. Everything that is listed, filtered or calculated comes from src/data/:

Export File What it holds
CHURCH church.ts Name, timeZone (IANA name, e.g. America/Chicago), timeZoneLabel, full address for calendar files, liveUrl, uidDomain
SERVICES church.ts The weekly schedule: day (0 = Sunday), start ("09:00"), minutes, name
KEEP_DEMO_CURRENT, DEMO_ANCHOR church.ts Moves demo dates forward (see Demo dates)
PEOPLE, STAFF people.ts Staff and speakers with role, email, bio and avatar; STAFF is the order on the about page
SERIES, BOOKS, SERMONS sermons.ts Sermon series (title, subtitle, art: table, sun, wheat or wave, blurb), book labels, every sermon
EVENT_CATS, EVENTS events.ts Event categories with their colour classes, and events
DAYS, AREAS, STAGES, GROUPS groups.ts Small-group filter labels and groups
GIVING giving.ts Amount chips, default amount, min and max, card fee, funds
VISIT visit.ts Number of Sundays offered, maximum party size, kids' rooms by age
NAV, MOBILE_NAV site.ts Menu items

Add a sermon to SERMONS (order doesn't matter; lists sort by date). TypeScript checks the keys:

ts
{
  id: "a-new-sermon", title: "A New Sermon", series: "open-table", part: 6, speaker: "dana",
  date: "2026-10-18", book: "luke", passage: "Luke 10:38–42", duration: 2100, audio: "",
  summary: "One or two sentences for cards and the page description.",
  verse: { text: "Quoted text.", ref: "Luke 10:41 (KJV)" },
  notes: [["Heading", "One short paragraph."], ["Heading", "Paragraph."]],
  questions: ["First question?", "Second question?"],
},

speaker is a PersonId (a key of PEOPLE), series a SeriesId, book a key of BOOKS, and duration is in seconds. A new person or series also needs its id added to the PersonId or SeriesId type. The demo quotes the King James Version, which is in the public domain; check the licence of any other translation before quoting it.

Add an event to EVENTS. Times are 24-hour, in the church's time zone; add weekly to repeat it every week until that date:

ts
{ id: "choir-night", title: "Choir Night", cat: "worship", date: "2026-11-05", weekly: "2026-12-17",
  start: "19:00", end: "20:30", place: "Main auditorium", summary: "Open rehearsal for anyone who sings." },

Add a group to GROUPS; day, area and stage must be keys of DAYS, AREAS and STAGES, and spots: 0 shows "Full: join the waitlist":

ts
{ id: "lunch-break", name: "Lunch Break", day: "wed", time: "12:15", area: "downtown", stage: "everyone",
  host: "Ana Ruiz", where: "Riverbend, Room 210", spots: 6, childcare: false, blurb: "A 45-minute study over lunch." },

Demo dates#

The demo is written for the week of DEMO_ANCHOR ("2026-10-11"). While KEEP_DEMO_CURRENT is true, every sermon and event date moves forward by whole weeks so that the latest sermon is always last Sunday and events are always upcoming. Weekdays never change. Set KEEP_DEMO_CURRENT = false in src/data/church.ts once you enter real dates, or your dates will be shifted too.

The shift is worked out by calendar() in src/lib/demo.ts, in the browser, from today's date in the church's time zone. Anything that depends on the clock (dates, the countdown, "Live now", the calendar, sermon lists) is rendered after the page loads, so the static HTML never holds a stale date and React never sees a hydration mismatch. The useMounted() hook in src/lib/mounted.ts is how components wait for that.

Images and artwork#

There are no photos. All artwork is SVG:

Artwork Where
Hero landscape (church, river, hills) Inline in src/app/page.tsx, 600×540
Welcome tent, small-group table, 404 signpost Inline in the visit, groups and not-found pages
Parking map Inline in src/app/visit/page.tsx, 640×420, with a <title> and <desc>
Staff portraits <Avatar id="dana" /> (src/components/art/Avatar.tsx), drawn from each person's avatar settings
Series covers <SeriesArt series="ruth" v={2} /> (src/components/art/SeriesArt.tsx), 320×200, varied by v (the sermon's part)
Logo src/components/Logo.tsx, and src/app/icon.svg (favicon)

A portrait's avatar object takes skin, hair, style (short, long, bun, curly, coily, waves or bald), bg, shirt, and optional glasses and beard. <Avatar> is decorative unless you pass a label, which becomes its alt text.

To use photos instead, replace <Avatar> or <SeriesArt> with <img> tags (files in public/, URLs through asset() from src/lib/asset.ts): portraits are square (1:1), series covers 16:10, and the hero frame about 10:9. Give every image meaningful alt text.

The social image is public/img/og-image.png (1200×630), rendered from og-image.svg. Edit the SVG, then export it to PNG at 1200×630.

The header and its menus are src/components/SiteHeader.tsx, built from NAV and MOBILE_NAV in src/data/site.ts; the footer is src/components/SiteFooter.tsx. To add a page, create src/app/<route>/page.tsx and add it to NAV (and to the footer lists if you like). The current page's link gets aria-current="page" automatically; /sermon counts as Sermons.

Features#

Service status and countdown#

<ServiceStatus> in the top bar shows "Next gathering: Sunday at 9:00 am", or "Live now" with a "Watch" link while a service is running. <Countdown> (home page and the sermons page; dark for the plum panel) shows days, hours, minutes and seconds to the next service, and switches to a "Live now" card with a "Watch live" button linking to CHURCH.liveUrl. <ThisSunday> shows the date of the next Sunday with a service still to come.

Everything comes from serviceState() in src/lib/services.ts, which reads SERVICES in CHURCH.timeZone, so a visitor in another time zone sees the right countdown for your church. Add a service to SERVICES:

ts
{ day: 3, start: "18:30", minutes: 60, name: "Midweek Worship" },

To preview the live state at any time, add ?live=1 to a URL, for example /?live=1. The ticking numbers are hidden from screen readers; a visually hidden sentence ("Starts in 5 days, 18 hours and 53 minutes.") gives the same information without announcing every second. Before the page has loaded in the browser, these show the plain service times.

Audio player#

<AudioPlayer sermon={s} /> (src/components/AudioPlayer.tsx) appears on the sermon page, on the home page (latest sermon) and in the "Watch live" panel on /sermons.

  • With a real file: set the sermon's audio to a URL ("/audio/room-at-the-table.mp3" for a file in public/audio/, or a full URL from your podcast host). The player then drives a real <audio> element: its duration comes from the file, and speed, seeking and progress use the media element.
  • Without a file (audio: "", the demo): playback is simulated over the sermon's duration with a timer, so every control works and a note under the player says so.

Controls: play/pause, a slider for scrubbing (5-second steps), back and forward 15 seconds, and a speed button cycling through SPEEDS (1×, 1.25×, 1.5×, 1.75×, 2×, 0.75×). Only one player plays at a time.

Keyboard, while focus is inside the player: Space (when not on a button) or K play/pause, J and L back and forward 15 seconds, arrow keys on the slider move 5 seconds, Home/End jump to the start or end. The slider has an aria-valuetext such as "12 minutes 5 seconds of 38 minutes 42 seconds", the play button's label switches between "Play" and "Pause", and speed changes are announced through a polite live region.

The listening position is saved (see Saved data); returning to a sermon shows "Resuming at 12:04" and sermon cards show "Resume at 12:04".

Sermon archive#

<SermonArchive> fills the series, speaker and book selects from the data and filters on every change. Search (after a short pause in typing, or on Enter) matches every word against the title, passage, summary, series and speaker. The filters are kept in the URL, so links can be shared: /sermons?series=open-table&speaker=dana&book=luke&q=table (names are case-insensitive; unknown values are ignored). Series cards toggle the series filter. The result count is a live region; with no results an empty state offers to clear the filters.

Sermon page#

/sermon?id=<id> (<SermonDetail>) shows one sermon; an unknown or missing id shows the latest sermon, with a notice for an unknown one. The page title and meta description follow the sermon. "Copy questions" copies the title, passage and numbered questions to the clipboard (it needs https or localhost; elsewhere it says copying failed). Related sermons are the rest of the series, then other sermons by the same speaker.

Events calendar#

<EventsCalendar> shows every occurrence of EVENTS (weekly events are expanded by calendar()). The month view is a table with one button per day; choosing a day shows its events in the "Selected day" panel, with the next three events after it. On wide screens each day lists up to two events and a "+n more" button. The list view shows weekly events once in an "Every week" box, then upcoming events by month.

Category pills filter both views. State is kept in the URL: /events?view=list&cat=kids,serve&month=2026-11. Opening an event adds event=<id>-<date>, so a link opens straight to that event.

The detail is <EventDialog>, a native <dialog> (focus stays inside it and Esc closes it; focus returns to the button that opened it). It opens for a click on any element with data-open-event="<key>", which is how <EventCard> works. "Add to calendar" downloads an .ics file built by buildIcs() in src/lib/ics.ts, with start and end times converted from the church's time zone to UTC, so it imports correctly into Google Calendar, Apple Calendar and Outlook wherever the visitor is.

Groups finder#

<GroupFinder> renders filter pills for day, area and season of life from DAYS, AREAS and STAGES, each with a live count; options that would give no results are disabled. "Childcare provided" is a checkbox. State is in the URL: /groups?day=tue,thu&area=west&stage=families&childcare=1. "Ask to join" links to /contact?topic=groups&group=<id>, which pre-fills the contact form.

Plan your visit#

<VisitForm> offers the next VISIT.sundays Sundays (computed in the church's time zone; today is skipped once the last Sunday service has started) and one radio card per Sunday service. Adults and children use steppers (typed numbers are clamped when the field changes); each child gets an age select, and the summary shows which room they go to from VISIT.rooms. Children in the last room (youth) count as an extra seat.

Giving#

<GiveForm> builds the amount chips from GIVING.amounts and the fund select from GIVING.funds. Typing your own amount clears the chips. "Cover the card fee" adds what is needed so that, after the fee in GIVING.fee (2.2% + $0.30 by default), the church receives the whole gift (feeFor()); the summary shows the total and what the church receives either way. /give?fund=pantry pre-selects a fund.

No payment is taken. See Connecting a payment provider.

Scroll reveal, header and back to top#

Elements with .reveal fade in as they scroll into view (src/components/Reveal.tsx); everything above the current element is revealed too, so jumping to an anchor never leaves gaps. With reduced motion, nothing is hidden or animated. The header gets a border and shadow once the page scrolls. The mobile menu (#menu-toggle) closes with Esc, after following a link and when the window grows to desktop width. To remove the back-to-top button, delete <BackToTop /> from layout.tsx and the .back-to-top styles in globals.css.

Forms#

All forms use useValidation() from src/components/forms/validation.tsx: errors appear on blur and submit and update as you type, the first invalid field is focused, each error is linked with aria-describedby, and a status line says "Please fix the highlighted fields."

Attribute Rule
required Must not be empty (checkboxes must be ticked, selects must have a value)
type="email", type="tel" Must look like an email address / phone number
minLength Minimum length
data-label Name used in the error message
data-novalidate Field is skipped (used by filters)
Form Component Behaviour
Newsletter (data-form="newsletter") NewsletterForm.tsx (home) Replaced with a thank-you message
Plan your visit (#visit-form) VisitForm.tsx Validates contact details and each child's age; confirms the Sunday, time and seats
Giving (#give-form) GiveForm.tsx Validates the amount (min and max from GIVING), name and email; explains nothing was charged
Contact (#contact-form) ContactForm.tsx Pre-filled from ?topic= and ?group=; thank-you message
Prayer (#prayer-form) PrayerForm.tsx Request required; email becomes required when "I'd like a pastor to get in touch" is ticked; the message reflects "Keep this private"

Demo forms don't send anything. Each form's onValid callback (the function passed to useValidation) is where to send it. For example, with Formspree in ContactForm.tsx:

tsx
const v = useValidation(async (form) => {
  const res = await fetch("https://formspree.io/f/your-id", {
    method: "POST",
    body: new FormData(form),
    headers: { Accept: "application/json" },
  });
  if (res.ok) setDone((form.querySelector<HTMLInputElement>("#c-name")?.value ?? "").trim().split(" ")[0]);
});

Every field has a name, so FormData carries all the values; keep a name on any field you add. Prayer requests can be sensitive: send them to a service and inbox only your pastoral team can read, and honour the private field.

If you remove static export (see Building and deploying), you can post to a Route Handler instead, for example src/app/api/contact/route.ts:

ts
export async function POST(request: Request) {
  const data = await request.formData();
  // Send data.get("email"), data.get("message")… to your inbox or CRM here.
  return Response.json({ ok: true });
}

Connecting a payment provider#

The giving form never collects card details. To take real gifts, change the onValid callback in GiveForm.tsx to call your server, which creates a checkout session with your provider (Stripe Checkout, PayPal, or a church platform such as Tithe.ly or Planning Center Giving) and returns its URL:

ts
const res = await fetch("/api/create-gift", { method: "POST", body: new FormData(form) });
const { url } = await res.json();
window.location.href = url;

That endpoint needs a server (a Route Handler without static export, or a function on your host). Recurring gifts (frequency is weekly or monthly) map to subscriptions in most providers. Many church platforms also offer an embeddable giving form you can use instead of this one.

Saved data (localStorage)#

Key Shape Used for
riverbend-sermon-progress (PROGRESS_KEY) { "room-at-the-table": 724 } (seconds) Resume position for each sermon

readProgress() in src/lib/progress.ts keeps only ids that exist in SERMONS and positions between 5 seconds and 5 seconds before the end; anything else (broken JSON, arrays, unknown ids) is dropped. A position is removed when a sermon finishes. If storage is blocked, the player works without saving. To reset, run localStorage.removeItem("riverbend-sermon-progress") in the browser console, or change PROGRESS_KEY to stop using old values.

SEO and sharing#

Each page exports metadata built by pageMeta(title, description) (src/lib/metadata.ts), which sets the title, description and Open Graph tags; the defaults and the Twitter card are in src/app/layout.tsx. The sermon page also updates its title and description from the sermon shown. Social sites need absolute image URLs: set your address when you build,

sh
NEXT_PUBLIC_SITE_URL=https://www.yourchurch.org pnpm build

and metadataBase turns /img/og-image.png into a full URL. Add a src/app/sitemap.ts and src/app/robots.ts (both work with static export) to list the pages. For search, consider Church structured data (a schema.org type) with your address and service times in a <script type="application/ld+json"> on the home page.

Accessibility#

  • Skip link, landmarks, one <h1> per page and a logical heading order.
  • aria-current="page" on the current navigation item; the mobile menu uses aria-expanded, closes with Esc and when the window grows to desktop width.
  • Visible focus outlines on every control; radio and checkbox cards show focus on the card.
  • The audio player is fully keyboard operable, with labelled buttons and a native range slider with spoken time.
  • The calendar is a table with a caption and column headers; day buttons have full-date labels with the number of events; the event detail is a modal <dialog>.
  • Filter results and counts are announced with role="status"; form errors are linked to their fields and the thank-you message is focused.
  • Illustrations have <title> text (the map also has a <desc>); decorative SVG is hidden from screen readers.
  • Colour contrast meets WCAG AA; prefers-reduced-motion turns off reveals, the live pulse and the river shimmer.

When editing, keep one <h1> per page, give every new input a <label>, and don't use apricot for text.

Building and deploying#

sh
pnpm build        # static site in out/
pnpm start        # preview out/

next.config.ts sets output: "export", images.unoptimized and basePath from DEMO_BASE_PATH. To host the site in a sub-folder, build with that folder (DEMO_BASE_PATH=/church pnpm build); links, scripts and asset() URLs all pick it up. Deploy out/ to any static host (Netlify, Cloudflare Pages, GitHub Pages, S3) or import the repository into Vercel. Most hosts serve 404.html for missing pages automatically. demo/ is the freeframe.dev demo build and can be deleted.

To use server features (Route Handlers, Server Actions), remove output: "export" from next.config.ts and deploy to Vercel or any Node.js host with next build and next start.

@typescript/native provides TypeScript 7's tsc for pnpm typecheck; typescript is aliased to @typescript/typescript6 because typescript-eslint needs the TypeScript 6 API. Replace both with typescript@7 once typescript-eslint supports it.

Troubleshooting#

  • The countdown is an hour off. Check CHURCH.timeZone is your church's IANA time zone (for example America/New_York), not a city name or an offset.
  • My sermons or events have the wrong dates. KEEP_DEMO_CURRENT is still true, so dates are moved forward. Set it to false in src/data/church.ts.
  • Dates and lists appear a moment after the page. That's by design: they depend on the visitor's clock and are rendered in the browser (see Demo dates).
  • The player says playback is simulated. The sermon's audio is empty. Add a URL to an audio file; if it is on another domain, that server must allow it to be played (most podcast hosts do).
  • A new sermon, event or group doesn't type-check. Its series, speaker or cat must be one of the ids in SeriesId, PersonId or EventCategoryId; add new ids to those types.
  • "Copy questions" fails. The clipboard needs https or localhost.
  • A class has no effect. Tailwind only sees class names written out in full; build them with complete names ("bg-plum"), not by joining fragments.
  • Images or links break in a sub-folder. Build with DEMO_BASE_PATH, use next/link for pages and asset() for files in public/.

License#

MIT: free for personal and commercial use. See LICENSE.txt in the download. The SVG artwork, logo, portraits and social image are part of the template and covered by the same license. The fonts in src/fonts/ are under the SIL Open Font License (see the *-OFL.txt files). Scripture quotations in the demo are from the King James Version, which is in the public domain.

Search documentation

Search the guides and every template's documentation