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:
150
app/api/bible/seo-url/route.ts
Normal file
150
app/api/bible/seo-url/route.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/db'
|
||||
|
||||
export const runtime = 'nodejs'
|
||||
|
||||
// Convert UUIDs to SEO-friendly slugs
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { versionId, bookId, chapter, locale } = await request.json()
|
||||
|
||||
if (!versionId || !bookId || !chapter) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Missing required parameters'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
const version = await prisma.bibleVersion.findUnique({
|
||||
where: { id: versionId }
|
||||
})
|
||||
|
||||
const book = await prisma.bibleBook.findUnique({
|
||||
where: { id: bookId }
|
||||
})
|
||||
|
||||
if (!version || !book) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Version or book not found'
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
const versionSlug = version.abbreviation.toLowerCase()
|
||||
const bookSlug = book.bookKey.toLowerCase()
|
||||
const seoUrl = `/${locale}/bible/${versionSlug}/${bookSlug}/${chapter}`
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
seoUrl,
|
||||
version: {
|
||||
id: version.id,
|
||||
name: version.name,
|
||||
abbreviation: version.abbreviation,
|
||||
slug: versionSlug
|
||||
},
|
||||
book: {
|
||||
id: book.id,
|
||||
name: book.name,
|
||||
bookKey: book.bookKey,
|
||||
slug: bookSlug
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error generating SEO URL:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// Convert SEO-friendly slugs to UUIDs
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const versionSlug = searchParams.get('version')
|
||||
const bookSlug = searchParams.get('book')
|
||||
const chapter = searchParams.get('chapter')
|
||||
|
||||
if (!versionSlug || !bookSlug || !chapter) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Missing required parameters'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
const version = await prisma.bibleVersion.findFirst({
|
||||
where: {
|
||||
abbreviation: {
|
||||
equals: versionSlug,
|
||||
mode: 'insensitive'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!version) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Version not found'
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
const book = await prisma.bibleBook.findFirst({
|
||||
where: {
|
||||
versionId: version.id,
|
||||
bookKey: {
|
||||
equals: bookSlug,
|
||||
mode: 'insensitive'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!book) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Book not found'
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
// Validate chapter exists
|
||||
const chapterNum = parseInt(chapter)
|
||||
const chapterRecord = await prisma.bibleChapter.findFirst({
|
||||
where: {
|
||||
bookId: book.id,
|
||||
chapterNum: chapterNum
|
||||
}
|
||||
})
|
||||
|
||||
if (!chapterRecord) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Chapter not found'
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
versionId: version.id,
|
||||
bookId: book.id,
|
||||
chapter: chapterNum,
|
||||
version: {
|
||||
id: version.id,
|
||||
name: version.name,
|
||||
abbreviation: version.abbreviation
|
||||
},
|
||||
book: {
|
||||
id: book.id,
|
||||
name: book.name,
|
||||
bookKey: book.bookKey,
|
||||
testament: book.testament
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error resolving SEO URL:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user