{"_id":"@code-plate/next-image-serve","name":"@code-plate/next-image-serve","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@code-plate/next-image-serve","version":"1.0.0","description":"Configurable Next.js App Router route handler for safely serving local images with ETag/304 support, in-memory LRU caching, and streaming for large files.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"sideEffects":false,"scripts":{"build":"tsup","dev":"tsup --watch","prepublishOnly":"npm run build"},"keywords":["nextjs","next","image","image-server","app-router","route-handler","lru-cache","etag"],"funding":{"type":"individual","url":"https://reymit.ir/mjavadsf"},"contributors":[{"name":"MJ. Soleymani Fard","email":"mjavadsf72@gmail.com","url":"https://github.com/MJavadSF"}],"author":{"name":"Mohammad Javad","email":"mjavadsf72@gmail.com","url":"https://github.com/MJavadSF"},"repository":{"type":"git","url":"git+https://github.com/MJavadSF/next-image-serve.git"},"bugs":{"url":"https://github.com/MJavadSF/next-image-serve/issues"},"homepage":"https://github.com/MJavadSF/next-image-serve#readme","license":"MIT","peerDependencies":{"next":">=13.4.0","react":">=18","react-dom":">=18"},"dependencies":{"lru-cache":"^11.5.2","mime":"^4.1.0"},"devDependencies":{"@types/mime":"^4.0.0","@types/node":"^26.2.0","next":"^16.3.0","tsup":"^8.5.1","typescript":"^6"},"engines":{"node":">=22"},"_id":"@code-plate/next-image-serve@1.0.0","_nodeVersion":"24.18.1","_npmVersion":"12.0.2","dist":{"integrity":"sha512-2f/suKRX86iodVf87D5jR4myHzTRoWnaYp6JTgIRjb1o+6m2XChlqnzEFjZXUJeo5xIReMZKSw1eW+6ux/svzA==","shasum":"c18f507a2755134166749a416c9cf2aa9cf13522","tarball":"https://registry.npmjs.org/@code-plate/next-image-serve/-/next-image-serve-1.0.0.tgz","fileCount":8,"unpackedSize":62750,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFTI5jChfbYUWR6aNWtmIkLQZdXctlOTj9RRhShzPnoSAiEA9YH0N0uBXNNrwE9c9hfgcOmXzJMyZJOJiVAIs2HL82g="}]},"_npmUser":{"name":"code-plate","email":"mjavadsf72@gmail.com"},"directories":{},"maintainers":[{"name":"code-plate","email":"mjavadsf72@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/next-image-serve_1.0.0_1786282327702_0.09149709020911057"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T13:32:07.544Z","1.0.0":"2026-08-09T13:32:07.864Z","modified":"2026-08-09T13:32:08.092Z"},"maintainers":[{"name":"code-plate","email":"mjavadsf72@gmail.com"}],"description":"Configurable Next.js App Router route handler for safely serving local images with ETag/304 support, in-memory LRU caching, and streaming for large files.","homepage":"https://github.com/MJavadSF/next-image-serve#readme","keywords":["nextjs","next","image","image-server","app-router","route-handler","lru-cache","etag"],"repository":{"type":"git","url":"git+https://github.com/MJavadSF/next-image-serve.git"},"contributors":[{"name":"MJ. Soleymani Fard","email":"mjavadsf72@gmail.com","url":"https://github.com/MJavadSF"}],"author":{"name":"Mohammad Javad","email":"mjavadsf72@gmail.com","url":"https://github.com/MJavadSF"},"bugs":{"url":"https://github.com/MJavadSF/next-image-serve/issues"},"license":"MIT","readme":"# @code-plate/next-image-serve\n\nA configurable **Next.js App Router** route handler for serving local images with in-memory LRU caching, `ETag`/`304` support, and automatic streaming for large files.\n\nInstead of writing this route handler from scratch for every project, you get one function — `createImageHandler(options)` — that returns a ready-to-export `GET` handler, with almost every behavior overridable.\n\n## Features\n\n- **Path traversal protection** — every segment is sanitized and the resolved path is checked against `basePath`\n- **Two-layer caching**\n  - Browser caching via a fully configurable `Cache-Control` header\n  - Server-side in-memory LRU cache (via `lru-cache`) for small files, togglable\n- **ETag + 304 responses** to avoid re-downloading unchanged files\n- **Automatic streaming** for files above a configurable size threshold, so large files are never fully buffered in memory\n- **Three addressing modes**:\n  - `\"query\"` (default) — `?folder=icons&file=logo.svg`, with fully renameable param names\n  - `\"path\"` — a Next.js catch-all segment, e.g. `/api/images/icons/logo.svg`\n  - `\"custom\"` — you supply your own function to extract folder/file from the request\n- **Configurable MIME whitelist**, headers, `Content-Disposition`, and error responses\n- Dual **ESM + CJS** build with full TypeScript types\n\n## Install\n\n```bash\nnpm install @code-plate/next-image-serve\n```\n\nPeer dependencies: `next >= 13.4`, `react >= 18`\n\n## Basic usage (query mode, default)\n\n```ts\n// app/api/images/route.ts\nimport path from \"path\";\nimport { createImageHandler } from \"@code-plate/next-image-serve\";\n\nexport const { GET } = createImageHandler({\n  basePath: path.join(process.cwd(), \"assets/images\"),\n});\n```\n\n```tsx\n// any component\nimport Image from \"next/image\";\n\n<Image\n  src=\"/api/images?folder=products&file=sneaker-01.webp\"\n  alt=\"Running sneaker\"\n  width={400}\n  height={400}\n  unoptimized\n/>;\n```\n\nOr build the URL with the type-safe helper:\n\n```tsx\nimport { buildImageUrl } from \"@code-plate/next-image-serve\";\n\nconst src = buildImageUrl({ base: \"/api/images\", folder: \"products\", file: \"sneaker-01.webp\" });\n// => \"/api/images?folder=products&file=sneaker-01.webp\"\n```\n\n### Renaming the query params\n\nEvery part of the query string is configurable — pick whatever names fit your API:\n\n```ts\nexport const { GET } = createImageHandler({\n  basePath: path.join(process.cwd(), \"assets/images\"),\n  queryParams: { folder: \"dir\", file: \"name\" },\n});\n```\n\n```tsx\nconst src = buildImageUrl({\n  base: \"/api/images\",\n  folder: \"products\",\n  file: \"sneaker-01.webp\",\n  queryParams: { folder: \"dir\", file: \"name\" },\n});\n// => \"/api/images?dir=products&name=sneaker-01.webp\"\n```\n\n## Path mode\n\nFor cleaner URLs like `/api/images/products/sneaker-01.webp`:\n\n```ts\n// app/api/images/[...segments]/route.ts\nimport { createImageHandler } from \"@code-plate/next-image-serve\";\n\nexport const { GET } = createImageHandler({ mode: \"path\" });\n```\n\n```tsx\nimport { buildImageUrl } from \"@code-plate/next-image-serve\";\n\nconst src = buildImageUrl({\n  base: \"/api/images\",\n  folder: \"products\",\n  file: \"sneaker-01.webp\",\n  mode: \"path\",\n});\n// => \"/api/images/products/sneaker-01.webp\"\n```\n\n## Custom mode\n\nWhen neither query nor path addressing fits — e.g. you want to read the file id from a signed token, a header, or a completely different URL shape — take full control:\n\n```ts\nexport const { GET } = createImageHandler({\n  mode: \"custom\",\n  basePath: path.join(process.cwd(), \"assets/avatars\"),\n  resolveSegments: async (req) => {\n    const userId = req.headers.get(\"x-user-id\");\n    return { folder: \"avatars\", file: `${userId}.webp` };\n  },\n});\n```\n\n## All options\n\n```ts\ncreateImageHandler({\n  // Absolute directory images are served from\n  basePath: path.join(process.cwd(), \"assets/images\"), // default: \"public/images\"\n\n  // How the request identifies the file\n  mode: \"query\", // \"query\" | \"path\" | \"custom\"\n\n  // Query param names (mode: \"query\" only) — name them anything you like\n  queryParams: { folder: \"folder\", file: \"file\" },\n\n  // Required when mode === \"custom\"\n  resolveSegments: async (req, ctx) => ({ folder: \"icons\", file: \"logo.svg\" }),\n\n  // Allowed MIME types\n  allowedTypes: [\"image/jpeg\", \"image/png\", \"image/webp\", \"image/svg+xml\", \"image/avif\"],\n\n  // Browser caching\n  browserCacheMaxAge: 86400, // seconds\n  immutable: true,\n  // Full override, takes precedence over the two options above:\n  cacheControl: (contentType) => \"public, max-age=31536000, immutable\",\n\n  // Content-Disposition header, or `false` to omit it entirely\n  contentDisposition: \"inline\", // \"inline\" | \"attachment\" | false\n\n  // X-Content-Type-Options: nosniff toggle\n  xContentTypeOptions: true,\n\n  // Extra headers merged into every 200 response\n  extraHeaders: (contentType) => ({ \"X-Served-By\": \"next-image-serve\" }),\n\n  // Buffer + server-cache files at/below this size; stream everything larger\n  streamThreshold: 512 * 1024, // bytes\n\n  // Server-side LRU cache\n  serverCache: {\n    enabled: true,\n    max: 200,\n    maxSize: 50 * 1024 * 1024,\n    ttl: 1000 * 60 * 60,\n  },\n\n  // Custom sanitizer applied to each path segment\n  sanitize: (segment) => segment ?? \"\",\n\n  // Custom error logger (defaults to console.error)\n  onError: (error, ctx) => logger.error(error, ctx),\n\n  // Per-case body/status overrides\n  responses: {\n    missingParam: { status: 400, body: { message: \"file parameter is required\" } },\n    invalidPath: { status: 400 },\n    notFound: { status: 404, body: { message: \"not found\" } },\n    invalidType: { status: 400 },\n    serverError: { status: 500 },\n  },\n});\n```\n\n## Notes\n\n- The server-side LRU cache only holds files at or below `streamThreshold`; larger files always stream and are never cached in memory, to keep memory usage predictable.\n- The cache is **per-instance/per-process**, not shared across replicas. This package doesn't replace a distributed cache (e.g. Redis) — it only caches within a single running process.\n- `basePath` must be an absolute filesystem path on the server, not a public URL.\n- Because the image is served from an API route rather than the built-in Next.js image loader, you'll typically want `unoptimized` on `next/image` (as in the examples above), or you can wire this route up as a [custom image loader](https://nextjs.org/docs/app/api-reference/components/image#loader).\n\n## Build\n\n```bash\nnpm run build\n```\n\nProduces a dual ESM (`dist/index.js`) + CJS (`dist/index.cjs`) build with type declarations (`dist/index.d.ts`), via `tsup`.\n","readmeFilename":"README.md","_rev":"1-c2854f84e65e24ec285281608b0fc7aa"}