Back to Blog
Web Development

Building a Modern KJV Bible App with Next.js and Supabase

A technical deep dive into building a full-featured Bible reading app with search, favorites, and topic organization using modern web technologies.

By William Simmons
#Next.js#React#Supabase#PostgreSQL#Bible App#TypeScript

Meta Description

Learn how we built a modern KJV Bible App using Next.js 15, Supabase, and React. Explore the architecture, database design, search implementation, and key features that make scripture accessible and interactive.


Introduction

Reading and studying the Bible should be accessible, fast, and intuitive. That's why we built a modern King James Version (KJV) Bible App that combines the power of Next.js 15 with Supabase's real-time database capabilities.

In this technical breakdown, we'll walk through the architecture, key features, and design decisions behind building a full-featured Bible reading application that handles over 31,000 verses with search, favorites, topics, and contextual reading.

Whether you're a developer looking to build a similar app or simply curious about how modern web technologies can enhance scripture study, this guide will give you an inside look at what it takes to build a production-ready Bible application.


The Tech Stack

We chose a modern, scalable stack that prioritizes performance and developer experience:

Core Framework

  • Next.js 15.1.5 (App Router) - Server-side rendering and optimal performance
  • React 18.3.1 - Component-based UI architecture
  • TypeScript 5.6.3 - Type safety and better developer experience

Styling & UI

  • Tailwind CSS 4.1.3 - Utility-first styling
  • Radix UI - 20+ accessible, unstyled UI primitives
  • Lucide React - Beautiful, consistent icon library
  • class-variance-authority - Type-safe component variants

Database & Backend

  • Supabase - PostgreSQL database with real-time capabilities
  • @supabase/supabase-js 2.75.0 - JavaScript client library

Additional Tools

  • react-hook-form - Efficient form management
  • sonner - Toast notifications
  • next-themes - Dark/light mode support

Database Architecture

Two-Table Design

The app uses a simple but effective two-table structure in Supabase:

1. bible_books Table

Stores metadata about each book of the Bible:

{
  book_id: number,        // Unique identifier
  book_name: string,      // E.g., "Genesis", "Matthew"
  testament: string,      // "Old Testament" or "New Testament"
  seq_number: number      // Sequential ordering (1-66)
}

This table maintains the canonical order of biblical books and enables efficient navigation.

2. bible_books_and_verses Table

Stores the actual scripture content:

{
  book_name: string,      // E.g., "Genesis"
  book_chapter: number,   // Chapter number
  verse_number: number,   // Verse number
  verse_text: string      // The actual verse content
}

With over 31,000 verses, this table is queried efficiently using indexed columns for book name and chapter number.

Supabase Configuration

The database client is configured in src/lib/supabase.ts:

import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

Key Features & Implementation

1. Bible Search

Search Method: PostgreSQL ILIKE Pattern Matching

The search uses Supabase's .ilike() method for case-insensitive pattern matching:

const searchVerses = async (query: string) => {
  const { data, error } = await supabase
    .from("bible_books_and_verses")
    .select("book_name, book_chapter, verse_number, verse_text")
    .ilike("verse_text", `%${query}%`) // Wildcard pattern matching
    .limit(100)
    .order("book_name")
    .order("book_chapter")
    .order("verse_number");

  // Transform and return results...
};

Search Characteristics:

  • Case-insensitive matching with %${query}% pattern
  • Limited to 100 results for performance
  • Results ordered by book → chapter → verse
  • Returns matching verses with full context

Note: This implementation uses simple pattern matching rather than PostgreSQL full-text search. For future improvements, implementing ts_vector and ts_query would provide more advanced search capabilities.

2. Chapter Reading

Users can browse the Bible by book and chapter with optimized queries:

const fetchChapter = async (book: string, chapter: number) => {
  const { data, error } = await supabase
    .from("bible_books_and_verses")
    .select("book_name, book_chapter, verse_number, verse_text")
    .eq("book_name", book) // Filter by book
    .eq("book_chapter", chapter) // Filter by chapter
    .order("verse_number"); // Sequential ordering

  return transformedVerses;
};

Features:

  • Fast chapter loading with indexed queries
  • Sequential verse ordering
  • Loading skeletons for better UX
  • Click any verse to expand to full chapter context

3. Contextual Reading

One unique feature is the ability to click any verse to see it in full chapter context:

const handleFetchChapter = async (book: string, chapter: number) => {
  const { data, error } = await supabase
    .from("bible_books_and_verses")
    .select("book_name, book_chapter, verse_number, verse_text")
    .eq("book_name", book)
    .eq("book_chapter", chapter)
    .order("verse_number");

  return mappedVerses;
};

This allows users to understand verses in their full biblical context without losing their place.

4. Favorites System

The favorites feature uses client-side state management with React:

// State management
const [favorites, setFavorites] = useState<Set<string>>(new Set());
const [favoriteVerses, setFavoriteVerses] = useState<Verse[]>([]);

// Toggle favorite with unique key
const handleToggleFavorite = (verse: Verse) => {
  const key = `${verse.book}_${verse.chapter}_${verse.verse}`;
  const newFavorites = new Set(favorites);

  if (newFavorites.has(key)) {
    newFavorites.delete(key); // Remove from favorites
  } else {
    newFavorites.add(key); // Add to favorites
  }

  setFavorites(newFavorites);
};

Key Format: Verses are identified using the pattern "Genesis_1_1" for Genesis 1:1.

Current Limitation: Favorites are stored in React state only and are lost on page refresh. Future enhancement would add localStorage or Supabase persistence.

5. Topics Organization

Users can create custom topics to organize verses by theme:

interface Topic {
  id: string; // Timestamp-based ID
  title: string;
  description: string;
  verses: Verse[];
  createdAt: Date;
}

const handleCreateTopic = (
  topicData: Omit<Topic, "id" | "createdAt">,
) => {
  const newTopic: Topic = {
    ...topicData,
    id: Date.now().toString(),
    createdAt: new Date(),
  };
  setTopics([...topics, newTopic]);
};

Features:

  • Create custom topics with title and description
  • Add verses from existing favorites
  • Search and add new verses directly to topics
  • Remove verses or delete entire topics
  • Share topics with copy/share actions

Current Limitation: Like favorites, topics are session-only and not persisted to the database.


Component Architecture

The app follows a clean, modular component structure:

Main Components

page.tsx (Root Component)

  • Central state management for favorites, topics, and navigation
  • Orchestrates data flow between all components
  • Handles tab routing and user interactions

BibleHeader.tsx (Navigation)

  • Tab navigation (Read, Books, Favorites, Topics)
  • Search bar with form submission
  • Book and chapter selection dropdowns
  • Loads Bible books from Supabase on mount

VerseDisplay.tsx (Read Tab)

  • Display verses with loading skeletons
  • Expandable verse context (click to see full chapter)
  • Favorite/copy/share actions per verse
  • Highlighted selected verse in context view

BooksExplorer.tsx (Books Tab)

  • Three-level navigation: Testament → Chapters → Verses
  • Grid layout for chapter selection
  • Hardcoded Bible structure (66 books + 18 Apocrypha)
  • Full chapter display with verse actions

Favorites.tsx (Favorites Tab)

  • Display all favorited verses
  • Remove favorites
  • Copy/share individual verses
  • Empty state prompts

Topics.tsx (Topics Tab)

  • Create and manage custom topics
  • Add verses from favorites or search
  • Inline search for new verses
  • Expandable editing interface
  • Topic-level and verse-level actions

Custom Hooks

useBibleApi.ts

export function useBibleApi() {
  return {
    verses: Verse[],
    loading: boolean,
    error: string | null,
    fetchChapter: (book, chapter) => Promise<void>,
    searchVerses: (query) => Promise<void>
  };
}

Centralizes all database queries and loading states.


Performance Considerations

Current Optimizations

Loading States

  • Skeleton loaders prevent layout shift
  • Loading indicators for async operations

Efficient State Management

  • Set<string> for O(1) favorites lookup
  • Key-based verse identification

Query Limits

  • Search limited to 100 results
  • Prevents excessive data transfer

Conditional Rendering

  • Only active tab component renders
  • Reduces unnecessary re-renders

Opportunities for Improvement

No Caching Strategy

  • Every navigation refetches from Supabase
  • Could implement React Query or SWR for caching
  • No localStorage for frequently accessed chapters

Missing Memoization

  • Could add useMemo for expensive computations
  • useCallback for event handlers passed to children
  • React.memo for pure components

No Search Debouncing

  • Search fires on form submit only
  • Could add debounced live search

No Pagination/Virtualization

  • All 100 search results render at once
  • Could use virtual scrolling for better performance

No Offline Support

  • No service worker or PWA capabilities
  • Could cache chapters for offline reading

Lessons Learned & Best Practices

What Worked Well

  1. Simple Database Schema - Two tables are sufficient for a Bible app
  2. Component Separation - Clear responsibilities make maintenance easy
  3. TypeScript - Caught many bugs before runtime
  4. Radix UI - Accessible components out of the box
  5. Tailwind CSS - Rapid UI development

What We'd Change

  1. Add Data Persistence - localStorage or Supabase for favorites/topics
  2. Implement Full-Text Search - PostgreSQL ts_vector for better search
  3. Add Caching - React Query for smarter data management
  4. Memoize Components - Reduce unnecessary re-renders
  5. Move Chapter Counts to DB - Remove hardcoded Bible structure

Future Enhancements

Planned Features

  • User Authentication - Save favorites and topics to user accounts
  • Notes & Highlights - Allow users to annotate verses
  • Reading Plans - Guided scripture reading schedules
  • Cross-References - Link related verses
  • Multiple Translations - Add ESV, NIV, NKJV support
  • Audio Bible - Listen to scripture
  • Offline Mode - PWA with service worker
  • Social Sharing - Share verses with custom images

Technical Improvements

  • Implement PostgreSQL full-text search with ranking
  • Add React Query for intelligent caching
  • Use virtual scrolling for long lists
  • Optimize bundle size with code splitting
  • Add comprehensive testing (Jest, React Testing Library)
  • Implement analytics to understand usage patterns

Conclusion

Building a Bible app with modern web technologies demonstrates how accessible and powerful scripture study can be when combined with thoughtful architecture and user experience design.

Our KJV Bible App handles over 31,000 verses with fast search, intuitive navigation, and organizational features like favorites and topics—all powered by Next.js 15 and Supabase.

While there's always room for improvement (persistence, caching, advanced search), the current implementation provides a solid foundation for a production-ready Bible reading experience.

Try the app: https://kjv-bible-app.vercel.app/

View the source: Available upon request for educational purposes.


About the Author

William Simmons is a full-stack developer specializing in Next.js, React, and modern web applications. He builds solutions that combine technical excellence with user-centered design.

Connect: Contact Premier Dev Solutions


Have questions about building a similar app or need help with your Next.js project? Get in touch to discuss how we can help.