feat: implement SEO-friendly URLs for Bible reader

- Add dynamic route structure /[locale]/bible/[version]/[book]/[chapter]
- Convert UUID-based URLs to readable format (e.g., /en/bible/eng-kjv/genesis/1)
- Implement automatic redirects from old URLs to new SEO-friendly format
- Add SEO metadata generation with proper titles, descriptions, and OpenGraph tags
- Create API endpoint for URL conversion between formats
- Update navigation in search results, bookmarks, and internal links
- Fix PWA manifest icons to correct dimensions (192x192, 512x512)
- Resolve JavaScript parameter passing issues between server and client components
- Maintain backward compatibility with existing bookmark and search functionality

Benefits:
- Improved SEO with descriptive URLs
- Better user experience with readable URLs
- Enhanced social media sharing
- Maintained full backward compatibility
This commit is contained in:
2025-09-28 23:17:58 +00:00
parent a01b2490dc
commit 61a5180e2f
10 changed files with 435 additions and 19 deletions

View File

@@ -1,6 +1,6 @@
'use client'
import { useState, useEffect, useRef, useCallback } from 'react'
import React, { useState, useEffect, useRef, useCallback } from 'react'
import { useTranslations, useLocale } from 'next-intl'
import { useAuth } from '@/hooks/use-auth'
import { useSearchParams, useRouter } from 'next/navigation'
@@ -128,7 +128,13 @@ const defaultPreferences: ReadingPreferences = {
readingMode: false
}
export default function BibleReaderNew() {
interface BibleReaderProps {
initialVersion?: string
initialBook?: string
initialChapter?: string
}
export default function BibleReaderNew({ initialVersion, initialBook, initialChapter }: BibleReaderProps = {}) {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
const t = useTranslations('pages.bible')
@@ -136,6 +142,31 @@ export default function BibleReaderNew() {
const router = useRouter()
const searchParams = useSearchParams()
const { user } = useAuth()
// Use initial props if provided, otherwise use search params
const effectiveParams = React.useMemo(() => {
if (initialVersion || initialBook || initialChapter) {
// Create a params-like object from the initial props
return {
get: (key: string) => {
if (key === 'version') return initialVersion || null
if (key === 'book') return initialBook || null
if (key === 'chapter') return initialChapter || null
if (key === 'verse') return searchParams.get('verse') // Still get verse from URL query params
return null
},
has: (key: string) => {
if (key === 'version') return !!initialVersion
if (key === 'book') return !!initialBook
if (key === 'chapter') return !!initialChapter
if (key === 'verse') return searchParams.has('verse')
return false
},
toString: (): string => ''
}
}
return searchParams
}, [initialVersion, initialBook, initialChapter, searchParams])
// Core state
const [books, setBooks] = useState<BibleBook[]>([])
@@ -313,10 +344,10 @@ export default function BibleReaderNew() {
// Handle URL parameters for bookmark navigation
useEffect(() => {
const urlVersion = searchParams.get('version')
const urlBook = searchParams.get('book')
const urlChapter = searchParams.get('chapter')
const urlVerse = searchParams.get('verse')
const urlVersion = effectiveParams.get('version')
const urlBook = effectiveParams.get('book')
const urlChapter = effectiveParams.get('chapter')
const urlVerse = effectiveParams.get('verse')
if (urlVersion && versions.length > 0) {
// Check if this version exists
@@ -353,25 +384,46 @@ export default function BibleReaderNew() {
}, 500)
}
}
}, [searchParams, versions, books, verses, selectedVersion, selectedBook, selectedChapter])
}, [effectiveParams, versions, books, verses, selectedVersion, selectedBook, selectedChapter])
// Function to update URL without causing full page reload
const updateUrl = useCallback((bookId?: string, chapter?: number, versionId?: string) => {
const params = new URLSearchParams()
const updateUrl = useCallback(async (bookId?: string, chapter?: number, versionId?: string) => {
const targetVersionId = versionId || selectedVersion
const targetBookId = bookId || selectedBook
const targetChapter = chapter || selectedChapter
if (versionId || selectedVersion) {
params.set('version', versionId || selectedVersion)
// Try to generate SEO-friendly URL
try {
const version = versions.find(v => v.id === targetVersionId)
const book = books.find(b => b.id === targetBookId)
if (version && book && targetChapter) {
// Generate SEO-friendly URL
const versionSlug = version.abbreviation.toLowerCase()
const bookSlug = book.bookKey.toLowerCase()
const newUrl = `/${locale}/bible/${versionSlug}/${bookSlug}/${targetChapter}`
router.replace(newUrl, { scroll: false })
return
}
} catch (error) {
console.error('Error generating SEO-friendly URL:', error)
}
if (bookId || selectedBook) {
params.set('book', bookId || selectedBook)
// Fallback to query parameter URL
const params = new URLSearchParams()
if (targetVersionId) {
params.set('version', targetVersionId)
}
if (chapter || selectedChapter) {
params.set('chapter', String(chapter || selectedChapter))
if (targetBookId) {
params.set('book', targetBookId)
}
if (targetChapter) {
params.set('chapter', String(targetChapter))
}
const newUrl = `/${locale}/bible?${params.toString()}`
router.replace(newUrl, { scroll: false })
}, [locale, selectedVersion, selectedBook, selectedChapter, router])
}, [locale, selectedVersion, selectedBook, selectedChapter, router, versions, books])
// Fetch books when version changes
useEffect(() => {