Eternal Vows is a customizable, self‑hosted wedding website template for sharing all of your event information in one place. Configure names, date, location, story, schedule, venue details (with map), registry links, FAQs, and optional photo‑sharing links via a simple YAML file—no rebuild required. JSON is also supported. A lightweight Node + Express server powers a dynamic background slideshow that auto‑refreshes as you add images. Run locally with Node or deploy easily with Docker/Compose.
- Clean, modern single-page design with elegant typography
- Dynamic background slideshow sourced from
config/photos(no rebuild required) - Smooth in-page navigation; external links open in a new tab
- Config-driven content (names, date, venues, schedule, registry, FAQs)
Below are screenshots of the default layout to help visualize the sections and spacing:
index.html— UI, styling, and client JS (loads details fromconfig/config.yaml)server.mjs— Express server and/api/photosendpointconfig/config.yaml— Site content (names, date, venue(s), etc.)config/photos/— Background slideshow images.github/workflows/docker.yml— Optional CI to build/push a container image
Requirements: Node 18+ recommended.
- Install dependencies
npm install- Start the server
npm start
# then open http://localhost:5500Optional: change the port via PORT env var (default 5500).
$env:PORT=8080; npm startEdit config/config.yaml. All fields are optional; unset sections/buttons are hidden automatically.
Notes:
-
JSON is also supported at
config/config.json. If both YAML and JSON exist, JSON takes precedence. -
coupleNames: Display names for the hero section -
dateDisplay: Friendly wedding date string -
locationShort: Short city/state line under the date -
story: Paragraph for “Our Story” -
schedule: Array of items{ time, title, details } -
venues: Multiple venues (preferred). Use an array[{...}, {...}]or a labeled object map.- Array form (recommended): each item supports
{ name, address, mapUrl, mapCta, notes, label? } - Object form: keys become labels; values are venue objects, e.g.
{ ceremony: {...}, reception: {...} }
- Array form (recommended): each item supports
-
venue: Legacy single-venue (optional):{ name, address, mapUrl, mapCta, notes } -
photoUpload:{ url, label?, text? }link to your shared album;textcustomizes the paragraph in the section (fallbacks to defaults if omitted) -
registry: Array of{ label, url } -
rsvp: Configure the RSVP section and button(s). Supports:- Object form:
{ url, label?, text?, buttons?: [{ label, url }, ...] }— first button is primary;textcustomizes the paragraph. - Array form:
[ { label, url }, { label, url } ]— multiple buttons. - Legacy flat keys (still supported):
rsvpUrl,rsvpLabel, and optionalrsvpText.
- Object form:
-
faqs: Array of{ q, a } -
slideshow:{ intervalMs, transitionMs, photoRefreshSeconds, dynamicPhotosUrl }(defaults work out of the box) -
ui:{ monogram, footerNote, autoRefreshSeconds }- Optional color palette:
ui.colorsor top-levelcolors.- Supported keys:
accent1,accent2,accent3,text,ink,bgOverlay,border,card,maxw,blur. - Values are CSS colors or raw CSS for
blur(e.g.,saturate(140%) blur(6px)) and unit values formaxw(e.g.,1024px).
- Supported keys:
- Background (fallback when photos aren’t visible):
ui.backgroundor top-levelbackground.- Keys:
baseTop,baseBottom,radial1,radial2(CSS colors). - Alternate keys also accepted in
ui.colorsfor convenience:bgBaseTop,bgBaseBottom,bgRadial1,bgRadial2(or kebab-casebg-base-top, etc.). - Defaults to a warm golden palette if not provided.
- Keys:
- Optional color palette:
Example (default with multiple venues):
coupleNames: "Partner One & Partner Two"
dateDisplay: "Month 00, 20XX"
locationShort: "City, ST"
story: "Share a short story about how you met..."
schedule:
- time: "3:00 PM"
title: "Ceremony"
details: "Ceremony location details"
- time: "4:00 PM"
title: "Cocktail Hour"
details: "Cocktail hour location details"
- time: "5:30 PM"
title: "Reception"
details: "Reception location details"
venues:
- label: "Ceremony"
name: "St. Mary Church"
address: "1 Church Rd"
mapUrl: "https://maps.google.com/?q=church"
notes: "Street parking available."
- label: "Reception"
name: "The Grand Hall"
address: "999 Party Ave"
mapUrl: "https://maps.google.com/?q=hall"
notes: "Valet on site."
photoUpload:
label: "Upload to Shared Album"
url: "https://example.com/album"
rsvp:
label: "RSVP Now"
url: "https://example.com/rsvp"
text: "Kindly respond by Sept 1st."
registry:
- label: "Amazon"
url: "https://example.com/amazon"
- label: "Target"
url: "https://example.com/target"
faqs:
- q: "Dress code?"
a: "Semi-formal."
slideshow:
dynamicPhotosUrl: "/api/photos"
intervalMs: 6000
transitionMs: 1200
ui:
monogram: "You’re invited to the wedding of"
footerNote: "With love, ..."
colors:
accent1: "#a3bcd6"
accent2: "#d7e5f3"
accent3: "#f7eddc"
text: "#ffffff"
ink: "#2b2a2a"
bgOverlay: "rgba(20,18,18,0.35)"
border: "rgba(255,255,255,0.12)"
card: "rgba(255,255,255,0.08)"
maxw: "1024px"
blur: "saturate(140%) blur(6px)"
background:
baseTop: "#2d2616"
baseBottom: "#1f1a10"
radial1: "rgba(234, 200, 94, 0.45)"
radial2: "rgba(255, 239, 189, 0.38)"RSVP variations:
Single object with extra buttons and custom text:
rsvp:
text: "Please let us know if you can join us."
url: "https://example.com/rsvp"
label: "RSVP Now"
buttons:
- label: "RSVP (English)"
url: "https://example.com/rsvp-en"
- label: "RSVP (Español)"
url: "https://example.com/rsvp-es"Array form (multiple buttons, no extra text):
rsvp:
- label: "RSVP Now"
url: "https://example.com/rsvp"
- label: "Add to Calendar"
url: "https://example.com/calendar.ics"Legacy flat keys (still work):
rsvpUrl: "https://example.com/rsvp"
rsvpLabel: "RSVP Now"
rsvpText: "Kindly respond by Sept 1st."Alternative formats for venues:
Single Venue:
venue:
name: "Venue Name"
address: "123 Main St, City, ST 00000"
mapUrl: "https://maps.google.com"
mapCta: "Open Map"
notes: "Parking and arrival notes."Multiple venues
venues:
Ceremony:
name: "Ceremony Venue Name"
address: "123 Chapel St, City, ST 00000"
mapUrl: "https://maps.google.com/?q=Ceremony+Venue"
mapCta: "Open Map"
notes: "Arrive 15 minutes early. Street parking available."
Reception:
name: "Reception Venue Name"
address: "999 Grand Ave, City, ST 00000"
mapUrl: "https://maps.google.com/?q=Reception+Venue"
mapCta: "Open Map"
notes: "Valet available. Dinner served at 6:00 PM."- Put images in
config/photos/. - Supported formats:
.jpg,.jpeg,.png,.webp,.avif. - The browser requests the list from
/api/photos. The client preloads the first image quickly and crossfades. - The list auto-refreshes every
slideshow.photoRefreshSecondsseconds (default 20) to pick up new files.
Tips:
- Use landscape images ≥ 1920×1080 for best results.
- Large images are fine; they’re loaded sequentially in the background.
Build a local image and run it. Mount your local config directory so you can edit content and photos without rebuilding.
Windows PowerShell:
docker build -t ghcr.io/jacoknapp/eternalvows:latest .
docker run --rm -p 5500:5500 -v ${PWD}\config:/app/config ghcr.io/jacoknapp/eternalvows:latestmacOS/Linux:
docker build -t ghcr.io/jacoknapp/eternalvows:latest .
docker run --rm -p 5500:5500 -v "$PWD/config:/app/config" ghcr.io/jacoknapp/eternalvows:latestOptional: change the port with -e PORT=8080 -p 8080:8080.
Example docker-compose.yml if you prefer compose:
services:
wedding:
image: ghcr.io/jacoknapp/eternalvows:latest # or build: .
ports:
- "5500:5500"
environment:
- PORT=5500
volumes:
- ./config:/app/config
restart: unless-stopped/.github/workflows/docker.yml builds a multi-arch image and can push to GHCR (GitHub Container Registry). You can enable Docker Hub pushes by adding a login step and including a docker.io image reference in the metadata step.
GET /api/photos→{ files: ["file1.jpg", "file2.png", ...] }fromconfig/photos/.
- Server exits immediately (code 1):
- Run
npm installfirst to ensure dependencies are present. - Check the terminal error message; ensure
config/config.yamlis valid YAML (or JSON if using JSON). - Ensure the
config/andconfig/photos/folders exist and are readable. - Port already in use? Set a different
PORTor stop the other process.
- Run
- Photos don’t appear:
- Verify images are in
config/photoswith supported extensions. - Check
http://localhost:5500/api/photosreturns a list. - Open DevTools → Network to see if image requests 404.
- Verify images are in
- Sharp install issues on Windows:
- Try
npm installagain; Sharp provides prebuilt binaries for most Node versions. - Ensure Node 18+ and a stable internet connection during install.
- Try
Keep private details out of version control. The provided config/config.yaml has placeholders—replace them locally or configure secrets in your deployment environment. JSON is also supported if preferred.
- All favicon assets live in
/favicon. - The primary source icon is
wedding_bell.svg. If you want the bell larger, edit that SVG (we scale it around center) and then regenerate the raster variants as needed. - This site already references: SVG, ICO, PNG sizes (16–256), Apple Touch (76–180), and
site.webmanifest.


