npm package: @howells/stow-next
Description: Next.js integration for Stow file storage
Latest version: 2.4.0

# @howells/stow-next

Next.js integration for [Stow](https://stow.sh) file storage. Includes route handler helpers and an image loader for `next/image`.

## Installation

```bash
npm install @howells/stow-next @howells/stow-server
# or
pnpm add @howells/stow-next @howells/stow-server
# or
yarn add @howells/stow-next @howells/stow-server
```

## Quick Start

### 1. Create an upload route

```typescript
// app/api/upload/route.ts
import { createUploadHandler } from "@howells/stow-next";
import { StowServer } from "@howells/stow-server";

const stow = new StowServer(process.env.STOW_API_KEY!);

export const POST = createUploadHandler({
  stow,
  maxSize: 10 * 1024 * 1024, // 10MB
  allowedTypes: ["image/*", "application/pdf"],
});
```

### 2. Use with @howells/stow-react

```tsx
// app/page.tsx
import { UploadDropzone } from "@howells/stow-react";

export default function Page() {
  return <UploadDropzone endpoint="/api/upload" onUploadComplete={(files) => console.log(files)} />;
}
```

## Route Handler

### `createUploadHandler(config)`

Creates a Next.js route handler for file uploads.

```typescript
import { createUploadHandler } from "@howells/stow-next";

export const POST = createUploadHandler({
  // Required: Stow server instance
  stow: new StowServer(process.env.STOW_API_KEY!),

  // Optional: File restrictions
  maxSize: 10 * 1024 * 1024, // Max file size in bytes
  allowedTypes: ["image/*", ".pdf"], // Allowed MIME types or extensions
  route: "uploads", // Default route/folder

  // Optional: Custom validation
  validate: async (file) => {
    if (file.name.includes("secret")) {
      return "Filename not allowed";
    }
    return true;
  },

  // Optional: Lifecycle hooks
  onUploadBegin: async (file) => {
    console.log(`Starting upload: ${file.name}`);
  },
  onUploadComplete: async (result) => {
    console.log(`Uploaded: ${result.url}`);
    // Save to database, etc.
  },
});
```

## Image Loader

Use Stow's image transformation with Next.js Image component.

### Setup

```javascript
// next.config.js
module.exports = {
  images: {
    loader: "custom",
    loaderFile: "./lib/stow-loader.ts",
  },
};
```

```typescript
// lib/stow-loader.ts
import { stowLoader } from "@howells/stow-next/image-loader";
export default stowLoader;
```

### Usage

```tsx
import Image from "next/image";

function MyComponent() {
  return (
    <Image
      src="https://stow.sh/files/bucket-id/image.jpg"
      alt="My image"
      width={800}
      height={600}
      quality={80}
    />
  );
}
```

### Custom Loader Config

```typescript
// lib/stow-loader.ts
import { createStowLoader } from "@howells/stow-next/image-loader";

export default createStowLoader({
  baseUrl: "https://stow.sh",
  defaultQuality: 80,
  defaultFormat: "webp",
});
```

## TypeScript

```typescript
import type { UploadHandlerConfig, StowLoaderConfig } from "@howells/stow-next";
```

## Complete Example

```typescript
// app/api/upload/route.ts
import { createUploadHandler } from "@howells/stow-next";
import { StowServer } from "@howells/stow-server";
import { db } from "@/lib/db";
import { auth } from "@/lib/auth";

const stow = new StowServer(process.env.STOW_API_KEY!);

export const POST = createUploadHandler({
  stow,
  maxSize: 5 * 1024 * 1024,
  allowedTypes: ["image/jpeg", "image/png", "image/webp"],

  validate: async (file) => {
    const session = await auth();
    if (!session) return "Unauthorized";
    return true;
  },

  onUploadComplete: async (result) => {
    const session = await auth();
    await db.insert(files).values({
      key: result.key,
      url: result.url,
      userId: session!.user.id,
    });
  },
});
```

## License

MIT