Secure server-side Scripture retrieval with layered caching and clean App Router architecture.
Last updated: February 2026
next: { revalidate: 86400 }npx create-next-app@latest verse-of-the-day
cd verse-of-the-day
Never expose API keys to the browser — only use them in Server Components or route handlers.
export BIBLEBRIDGE_API_KEY=your_api_key_here
Restart the development server after setting environment variables.
This example fetches a curated set of popular verses once and caches them:
revalidate: 86400
Create lib/getVerses.ts:
export type VerseType = {
book: { name: string };
chapter: number;
data: {
verse: number;
text: string;
};
};
// Top 10 popular verses used for the example demo.
// These are fetched once and cached for 24 hours.
const VERSES = [
{ bookID: "19", chapter: "23", verse: "1" }, // Psalm 23:1
{ bookID: "43", chapter: "3", verse: "16" }, // John 3:16
{ bookID: "20", chapter: "3", verse: "5" }, // Proverbs 3:5
{ bookID: "45", chapter: "8", verse: "28" }, // Romans 8:28
{ bookID: "23", chapter: "41", verse: "10" }, // Isaiah 41:10
{ bookID: "19", chapter: "46", verse: "1" }, // Psalm 46:1
{ bookID: "50", chapter: "4", verse: "13" }, // Philippians 4:13
{ bookID: "24", chapter: "29", verse: "11" }, // Jeremiah 29:11
{ bookID: "40", chapter: "5", verse: "16" }, // Matthew 5:16
{ bookID: "58", chapter: "11", verse: "1" }, // Hebrews 11:1
];
// In-memory cache (per server instance)
// Prevents repeated fetch calls during the same runtime session.
let cached: VerseType[] | null = null;
export async function getCachedVerses(): Promise {
// Serve from memory if already fetched
if (cached) return cached;
const key = process.env.BIBLEBRIDGE_API_KEY?.trim();
if (!key) {
throw new Error("Missing BIBLEBRIDGE_API_KEY");
}
const results: VerseType[] = [];
for (const v of VERSES) {
const url =
`https://holybible.dev/api/scripture` +
`?bookID=${v.bookID}` +
`&chapter=${v.chapter}` +
`&verse=${v.verse}` +
`&version=KJV`;
const res = await fetch(url, {
method: "GET",
headers: {
// Bearer auth keeps API key server-side only
Authorization: `Bearer ${key}`,
},
// Cache response for 24 hours at the Next.js layer
next: { revalidate: 86400 },
});
if (!res.ok) {
const body = await res.text();
console.error("URL:", url);
console.error("Status:", res.status);
console.error("Body:", body);
throw new Error(
`Failed to fetch ${v.bookID}-${v.chapter}-${v.verse}`
);
}
results.push(await res.json());
}
// Store results in memory cache
cached = results;
return results;
}
Scripture retrieval works with a free API key. Caching reduces upstream API calls.
Server Components fetch Scripture securely and choose an initial verse index on the server — this prevents hydration mismatch.
import { getCachedVerses } from "@/lib/getVerses";
import VerseActions from "@/components/VerseActions";
export default async function Home() {
// Fetch verses server-side (cached for 24 hours)
const verses = await getCachedVerses();
// Select initial verse on the server to avoid hydration mismatch
const initialIndex = Math.floor(Math.random() * verses.length);
return (
<main className="min-h-screen flex flex-col items-center justify-center p-8">
<VerseActions
verses={verses}
initialIndex={initialIndex}
/>
</main>
);
}
Client Components handle interaction — cycling verses, copy-to-clipboard, and social sharing. The shuffle logic ensures each verse is shown once per cycle before reshuffling.
"use client";
import { useState, useMemo } from "react";
type VerseType = {
book: { name: string };
chapter: number;
data: { verse: number; text: string };
};
type Props = {
verses: VerseType[];
initialIndex: number;
};
function shuffle(array: number[]) {
const arr = [...array];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
export default function VerseActions({ verses, initialIndex }: Props) {
const safeInitialIndex =
typeof initialIndex === "number" &&
initialIndex >= 0 &&
initialIndex < verses.length
? initialIndex
: 0;
const initialOrder = useMemo(() => {
const indexes = verses.map((_, i) => i);
const shuffled = shuffle(indexes);
const startIndex = shuffled.indexOf(safeInitialIndex);
if (startIndex > 0) {
[shuffled[0], shuffled[startIndex]] = [
shuffled[startIndex],
shuffled[0],
];
}
return shuffled;
}, [verses, safeInitialIndex]);
const [order, setOrder] = useState(initialOrder);
const [position, setPosition] = useState(0);
const verse = verses[order[position]];
if (!verse) return null;
function getNewVerse() {
if (position < order.length - 1) {
setPosition(position + 1);
} else {
const newOrder = shuffle(verses.map((_, i) => i));
setOrder(newOrder);
setPosition(0);
}
}
return (
<div>
<h1>{verse.book.name} {verse.chapter}:{verse.data.verse}</h1>
<p>{verse.data.text}</p>
<button onClick={getNewVerse}>Get Another Verse</button>
</div>
);
}
Client-side logic is purely interactive — API access remains server-side.
This route returns a single random verse using the cached dataset — no additional upstream API calls are required.
import { NextResponse } from "next/server";
import { getCachedVerses } from "@/lib/getVerses";
export async function GET() {
try {
const verses = await getCachedVerses();
if (!Array.isArray(verses) || verses.length === 0) {
return NextResponse.json(
{ error: "Invalid verse data" },
{ status: 500 }
);
}
const random =
verses[Math.floor(Math.random() * verses.length)];
return NextResponse.json(random);
} catch (error) {
console.error("Verse route error:", error);
return NextResponse.json(
{ error: "Failed to load verse" },
{ status: 500 }
);
}
}
Deploy this example using Vercel’s one-click flow.
After clicking the button:
BIBLEBRIDGE_API_KEYBIBLEBRIDGE_API_KEY=your_api_key_here
This example demonstrates production-safe Scripture integration — server-only API keys, layered caching, hydration-safe rendering, and reusable server logic.
BibleBridge provides canonical Scripture data while your application controls presentation, interaction, and deployment.
revalidate: 86400 is set
Official GitHub repository:
https://github.com/ZeroCoolZiemer/biblebridge-nextjs-verse-of-the-day
For full endpoint details, see the BibleBridge API documentation.