SDKs
Next.js

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/nextjs

Environment 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.ai

Note: 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.POST

The unified handler automatically detects the request type based on the payload:

  • Click Event: Contains clicked_url and search_id
  • 404 Event: Contains raw_path only
  • Suggest: Contains path field

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).