Next.js SDK
The @deadendai/nextjs (opens in a new tab) package provides server-side API handlers and client-side examples for Next.js applications.
Installation
npm install @deadendai/nextjs
# or
yarn add @deadendai/nextjs
# or
pnpm add @deadendai/nextjsEnvironment Variables
Create a .env.local file in your Next.js project root:
DEADEND_API_KEY=da_live_your_api_key_here
DEADEND_HOST=https://api.deadend.aiNote: DEADEND_HOST is optional and defaults to https://api.deadend.ai if not provided. Never expose your API key to the browser - this package is designed for server-side use only.
Server Setup
Create a unified API route handler at app/api/deadend/[[...path]]/route.ts:
import { createDeadendHandlers, SearchThreshold } from '@deadendai/nextjs'
const handlers = createDeadendHandlers({
limit: 5,
searchThreshold: SearchThreshold.GOOD,
})
export const POST = handlers.POSTThe unified handler automatically detects the request type based on the payload:
- Click Event: Contains
clicked_urlandsearch_id - 404 Event: Contains
raw_pathonly - Suggest: Contains
pathfield
Client Usage Examples
Here are two complete examples for your app/not-found.tsx file:
Automatic Redirect - Auto-redirect to top suggestion
This implementation automatically redirects users to the top suggestion when they hit a 404 page:
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export default function NotFound() {
const pathname = usePathname()
useEffect(() => {
async function handleNotFound() {
const path = pathname || window.location.pathname
// Track the 404 event
fetch('/api/deadend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
raw_path: path,
origin: window.location.origin,
referrer: document.referrer || undefined,
}),
}).catch(() => {}) // Silently fail - don't block UX
// Fetch top suggestion (limit: 1)
try {
const response = await fetch('/api/deadend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path }),
})
if (response.ok) {
const data = await response.json()
if (data.suggestions && data.suggestions.length > 0) {
const suggestion = data.suggestions[0]
// Track click event
fetch('/api/deadend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clicked_url: suggestion.url,
clicked_rank: 1,
raw_path: path,
search_id: data.search_id,
origin: window.location.origin,
}),
}).catch(() => {}) // Silently fail
// Redirect to suggestion
window.location.href = suggestion.url
return
}
}
} catch (error) {
console.error('Failed to fetch suggestions:', error)
}
}
handleNotFound()
}, [pathname])
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-4xl font-bold mb-4">404 - Page Not Found</h1>
<p className="text-gray-600 mb-4">Redirecting to a similar page...</p>
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
</div>
)
}Features:
- ✅ Automatically tracks 404 event
- ✅ Fetches single top suggestion
- ✅ Automatically tracks click event
- ✅ Immediate redirect to best match
- ✅ Graceful fallback if no suggestions found
User Choice - Present suggestions for user selection
This implementation shows users multiple suggestions and lets them choose which page to visit:
'use client'
import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
interface Suggestion {
url: string
title: string
description?: string
score: number
}
interface SuggestResponse {
search_id: string
suggestions: Suggestion[]
}
export default function NotFound() {
const pathname = usePathname()
const [suggestions, setSuggestions] = useState<Suggestion[]>([])
const [searchId, setSearchId] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
const [currentPath, setCurrentPath] = useState('')
useEffect(() => {
const path = pathname || window.location.pathname
setCurrentPath(path)
async function fetchSuggestionsAndTrack() {
try {
// Track the 404 event
fetch('/api/deadend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
raw_path: path,
origin: window.location.origin,
referrer: document.referrer || undefined,
}),
}).catch(() => {}) // Silently fail - don't block UX
// Fetch suggestions
const response = await fetch('/api/deadend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path }),
})
if (response.ok) {
const data: SuggestResponse = await response.json()
setSuggestions(data.suggestions || [])
setSearchId(data.search_id)
}
} catch (error) {
console.error('Failed to fetch suggestions:', error)
} finally {
setLoading(false)
}
}
fetchSuggestionsAndTrack()
}, [pathname])
const handleSuggestionClick = async (url: string, rank: number) => {
if (searchId) {
// Track the click event
fetch('/api/deadend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clicked_url: url,
clicked_rank: rank,
raw_path: currentPath,
search_id: searchId,
origin: window.location.origin,
}),
}).catch(() => {}) // Silently fail
}
}
return (
<main className="min-h-screen flex items-center justify-center p-8">
<div className="max-w-2xl w-full">
{/* 404 Header */}
<div className="text-center mb-12">
<h1 className="text-6xl font-bold mb-4">404</h1>
<h2 className="text-2xl font-semibold mb-3">Page not found</h2>
<p className="text-gray-600">
The page <code className="text-blue-600 bg-gray-100 px-2 py-1 rounded font-mono text-sm">{currentPath}</code> doesn't exist.
</p>
</div>
{/* Suggestions Section */}
<div className="space-y-4">
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
<span className="ml-3 text-gray-600">Finding similar pages...</span>
</div>
) : suggestions.length > 0 ? (
<>
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-900 mb-2">
Were you looking for one of these?
</h3>
</div>
<div className="space-y-3">
{suggestions.map((suggestion, index) => (
<Link
key={suggestion.url}
href={suggestion.url}
onClick={() => handleSuggestionClick(suggestion.url, index + 1)}
className="group block p-4 rounded-lg border border-gray-200 hover:border-blue-500 hover:bg-blue-50 transition-all"
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<h4 className="text-gray-900 font-medium group-hover:text-blue-600 transition-colors">
{suggestion.title || suggestion.url}
</h4>
{suggestion.description && (
<p className="text-gray-600 text-sm mt-1 line-clamp-2">
{suggestion.description}
</p>
)}
<p className="text-gray-500 text-xs mt-2 font-mono truncate">
{suggestion.url}
</p>
</div>
<div className="flex items-center gap-3 flex-shrink-0">
<span className="text-xs text-gray-500 font-mono">
{Math.round(suggestion.score * 100)}%
</span>
<svg
className="w-4 h-4 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-1 transition-all"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</Link>
))}
</div>
</>
) : (
<div className="text-center py-8">
<p className="text-gray-600">No similar pages found.</p>
</div>
)}
{/* Home Button */}
<div className="flex justify-center pt-8">
<Link
href="/"
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-gray-900 text-white font-medium hover:bg-gray-800 transition-colors"
>
Back to Home
</Link>
</div>
</div>
</div>
</main>
)
}Features:
- ✅ Tracks 404 event on page load
- ✅ Fetches multiple suggestions (default: 3)
- ✅ Displays suggestions with relevance scores
- ✅ Tracks click events when user selects a suggestion
- ✅ Clean, user-friendly interface
- ✅ Loading and empty states
Note: This example uses basic Tailwind CSS classes. You can enhance it with animations using libraries like framer-motion or customize the styling to match your design system.
Additional Resources
For more details, see the @deadendai/nextjs documentation (opens in a new tab).