SonusLab StorageSonusLab Storage

Next.js

App Router adapter — one route handler on the server, one hook on the client.

Route handler

createUploadRoute returns a single POST handler that serves both the presign and complete steps. The client SDK distinguishes them by an action field in the request body — you don't need two routes.

app/api/upload/route.ts
// app/api/upload/route.ts
import { StorageClient } from 'sonuslab-storage/server'
import { createUploadRoute } from 'sonuslab-storage/react'

const storage = new StorageClient({
  apiKey: process.env.SONUSLAB_STORAGE_API_KEY!,
})

export const { POST } = createUploadRoute({ storage })

Client component

Both presignEndpoint and completeEndpoint point at the same route.

components/Uploader.tsx
'use client'

import { useUpload } from 'sonuslab-storage/react'

export function Uploader() {
  const { upload, status, progress, error, reset } = useUpload({
    presignEndpoint: '/api/upload',
    completeEndpoint: '/api/upload',
  })

  return (
    <div>
      <input
        type="file"
        disabled={status === 'uploading'}
        onChange={(e) => {
          const file = e.target.files?.[0]
          if (file) upload(file)
        }}
      />
      {status === 'uploading' && <p>{progress}%</p>}
      {status === 'error' && (
        <>
          <p>{error?.message}</p>
          <button onClick={reset}>Retry</button>
        </>
      )}
    </div>
  )
}

Hook return shape

ParameterTypeDescription
upload(file: File, metadata?: Record<string, unknown>) => Promise<UploadResult>Start the upload. Resolves with fileId + key.
status'idle' | 'uploading' | 'complete' | 'error'Current state.
progress{ loaded, total, percent } | nullPercent complete during the PUT.
errorError | nullSet when status is error.
reset() => voidClear back to idle.

Lifecycle callbacks

Pass these in useUpload({ ... }) for analytics or toasts without depending on useEffect.

ParameterTypeDescription
onStart(file: File) => voidFires when upload() is invoked, before any network I/O.
onComplete(result: UploadResult) => voidFires after the complete step resolves.
onError(err: Error) => voidFires when the upload rejects (after status becomes error).

Auth

Use the onPresign hook to authenticate the user and inject metadata before the SDK mints the upload URL. Throwing a Response short-circuits the request.

app/api/upload/route.ts
export const { POST } = createUploadRoute({
  storage,
  onPresign: async (req, body) => {
    const user = await getUserFromCookie(req)
    if (!user) throw new Response('Unauthorized', { status: 401 })
    return { metadata: { userId: user.id } }
  },
})

Server-only

sonuslab-storage/server must only be imported in route handlers, server actions, or other server-only files. The API key must never reach the client bundle.