Skip to content
bknd-serve-files logo

Serve Files

bknd-serve-files

Use when serving uploaded files to users. Covers API-proxied file serving, direct storage URLs (S3/R2/Cloudinary), CDN configuration, public file URLs, caching headers, image optimization with Cloudinary, and serving files in frontend applications.

NeverSight/skills_feed0installs215stars

SKILL.md

Full skill instructions

Serve Files

Serve uploaded files from Bknd storage to users via API proxy or direct storage URLs.

Prerequisites

  • Media module enabled in Bknd config
  • Storage adapter configured (S3, R2, Cloudinary, or local)
  • Files uploaded via bknd-file-upload skill
  • For CDN: storage provider with CDN support (S3/​R2/​Cloudinary)

When to Use UI Mode

  • Admin panel > Media section > view/​preview files
  • Copy file URLs from admin interface
  • Quick verification that files are accessible

When to Use Code Mode

  • Build file URLs programmatically
  • Configure CDN or custom domains
  • Implement image optimization
  • Access control for file downloads

File Serving Methods

Bknd supports two approaches to serve files:

MethodUse CasePerformanceControl
API ProxySimple setup, private filesModerateFull (auth, permissions)
Direct URLHigh traffic, public filesBest (CDN)Limited (bucket ACLs)

Step-by-Step: API Proxy (via Bknd)

Files served through Bknd API at /​api/​media/​file/​{filename}.

Step 1: Get File URL

import { Api } from "bknd";

const api = new Api({ host: "http://localhost:7654" });

// Build file URL
const fileUrl = `${api.host}/​api/​media/​file/​image.png`;
// "http://localhost:7654/​api/​media/​file/​image.png"

Step 2: Display in Frontend

function Image({ filename }) {
  const { api } = useApp();
  const src = `${api.host}/​api/​media/​file/​${filename}`;

  return <img src={src} alt="" />;
}

Step 3: Download File (SDK)

// Get as File object
const file = await api.media.download("image.png");

// Get as stream (for large files)
const stream = await api.media.getFileStream("image.png");

Step 4: Verify Access

# Test file access
curl -I http://localhost:7654/​api/​media/​file/​image.png

# Response includes:
# Content-Type: image/​png
# Content-Length: 12345
# ETag: "abc123..."

Step-by-Step: Direct Storage URLs

Serve files directly from S3/​R2/​Cloudinary for better performance.

S3/​R2 Direct URLs

// S3 URL pattern
const s3Url = `https://${bucket}.s3.${region}.amazonaws.com/​${filename}`;
// "https://mybucket.s3.us-east-1.amazonaws.com/​image.png"

// R2 URL pattern (public bucket)
const r2Url = `https://${customDomain}/​${filename}`;
// "https://media.myapp.com/​image.png"

Cloudinary Direct URLs

Cloudinary provides automatic CDN and transformations:

// Basic URL
const cloudinaryUrl = `https://res.cloudinary.com/​${cloudName}/​image/​upload/​${filename}`;

// With transformations
const optimizedUrl = `https://res.cloudinary.com/​${cloudName}/​image/​upload/​w_800,q_auto,f_auto/​${filename}`;

Building URLs in Code

// Helper to get direct URL based on adapter type
function getFileUrl(filename: string, config: MediaConfig): string {
  const { adapter } = config;

  switch (adapter.type) {
    case "s3":
      // S3/​R2 URL from configured endpoint
      return `${adapter.config.url}/​${filename}`;

    case "cloudinary":
      return `https://res.cloudinary.com/​${adapter.config.cloud_name}/​image/​upload/​${filename}`;

    case "local":
      // Always use API proxy for local
      return `/​api/​media/​file/​${filename}`;

    default:
      return `/​api/​media/​file/​${filename}`;
  }
}

CDN Configuration

Cloudflare R2 with Custom Domain

  1. Create R2 bucket in Cloudflare dashboard

  2. Enable public access on bucket

  3. Configure custom domain (Cloudflare DNS):

    • Add CNAME: media.yourapp.com -> <bucket>.<account>.r2.dev
  4. Use in Bknd config:

export default defineConfig({
  media: {
    enabled: true,
    adapter: {
      type: "s3",
      config: {
        access_key: process.env.R2_ACCESS_KEY,
        secret_access_key: process.env.R2_SECRET_KEY,
        url: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/​${process.env.R2_BUCKET}`,
      },
    },
  },
});
  1. Serve files via custom domain:
const publicUrl = `https://media.yourapp.com/​${filename}`;

AWS S3 with CloudFront

  1. Create S3 bucket with public read (or CloudFront OAI)

  2. Create CloudFront distribution:

    • Origin: S3 bucket
    • Cache policy: CachingOptimized
    • Custom domain (optional)
  3. Use CloudFront URL:

const cdnUrl = `https://d123abc.cloudfront.net/​${filename}`;
// Or with custom domain
const cdnUrl = `https://cdn.yourapp.com/​${filename}`;

Cloudinary (Built-in CDN)

Cloudinary includes global CDN automatically:

export default defineConfig({
  media: {
    enabled: true,
    adapter: {
      type: "cloudinary",
      config: {
        cloud_name: "your-cloud-name",
        api_key: process.env.CLOUDINARY_API_KEY,
        api_secret: process.env.CLOUDINARY_API_SECRET,
      },
    },
  },
});

Files served from res.cloudinary.com with global CDN.

Image Optimization

Cloudinary Transformations

// Build optimized image URL
function getOptimizedImage(filename: string, options: {
  width?: number;
  height?: number;
  quality?: "auto" | number;
  format?: "auto" | "webp" | "avif" | "jpg" | "png";
  crop?: "fill" | "fit" | "scale" | "thumb";
} = {}) {
  const cloudName = process.env.CLOUDINARY_CLOUD_NAME;
  const transforms: string[] = [];

  if (options.width) transforms.push(`w_${options.width}`);
  if (options.height) transforms.push(`h_${options.height}`);
  if (options.quality) transforms.push(`q_${options.quality}`);
  if (options.format) transforms.push(`f_${options.format}`);
  if (options.crop) transforms.push(`c_${options.crop}`);

  const transformStr = transforms.length > 0 ? transforms.join(",") + "/" : "";

  return `https://res.cloudinary.com/​${cloudName}/​image/​upload/​${transformStr}${filename}`;
}

// Usage
const thumb = getOptimizedImage("avatar.png", {
  width: 100,
  height: 100,
  crop: "fill",
  quality: "auto",
  format: "auto",
});
// "https://res.cloudinary.com/​mycloud/​image/​upload/​w_100,h_100,c_fill,q_auto,f_auto/​avatar.png"

Common Transformation Patterns

// Responsive images
const srcSet = [400, 800, 1200].map(w =>
  `${getOptimizedImage(filename, { width: w, format: "auto" })} ${w}w`
).join(", ");

// Thumbnail generation
const thumb = getOptimizedImage(filename, {
  width: 150,
  height: 150,
  crop: "thumb",
});

// Automatic format (WebP/​AVIF when supported)
const optimized = getOptimizedImage(filename, {
  quality: "auto",
  format: "auto",
});

React Integration

Image Component with Fallback

function StoredImage({ filename, alt, ...props }) {
  const { api } = useApp();
  const [error, setError] = useState(false);

  // API proxy URL as fallback
  const apiUrl = `${api.host}/​api/​media/​file/​${filename}`;

  // Direct CDN URL (configure based on your adapter)
  const cdnUrl = `https://media.yourapp.com/​${filename}`;

  return (
    <img
      src={error ? apiUrl : cdnUrl}
      alt={alt}
      onError={() => setError(true)}
      {...props}
    />
  );
}

Responsive Image Component

function ResponsiveImage({ filename, alt, sizes = "100vw" }) {
  const cloudName = process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME;
  const base = `https://res.cloudinary.com/​${cloudName}/​image/​upload`;

  const srcSet = [400, 800, 1200, 1600].map(w =>
    `${base}/​w_${w},q_auto,f_auto/​${filename} ${w}w`
  ).join(", ");

  return (
    <img
      src={`${base}/​w_800,q_auto,f_auto/​${filename}`}
      srcSet={srcSet}
      sizes={sizes}
      alt={alt}
      loading="lazy"
    />
  );
}

File Download Button

function DownloadButton({ filename, label }) {
  const { api } = useApp();
  const [downloading, setDownloading] = useState(false);

  const handleDownload = async () => {
    setDownloading(true);
    try {
      const file = await api.media.download(filename);

      // Create download link
      const url = URL.createObjectURL(file);
      const link = document.createElement("a");
      link.href = url;
      link.download = filename;
      link.click();
      URL.revokeObjectURL(url);
    } catch (err) {
      console.error("Download failed:", err);
    } finally {
      setDownloading(false);
    }
  };

  return (
    <button onClick={handleDownload} disabled={downloading}>
      {downloading ? "Downloading..." : label || "Download"}
    </​button>
  );
}

Caching Configuration

S3/​R2 Cache Headers

Set cache headers when uploading:

// Custom adapter with cache headers (advanced)
// S3 adapter doesn't expose this directly; configure via bucket policy
// or CloudFront cache behaviors

Cloudflare R2 Cache Rules

In Cloudflare dashboard:

  1. Go to Caching > Cache Rules
  2. Create rule for your R2 subdomain
  3. Set Edge TTL (e.g., 1 year for immutable assets)

API Proxy Caching

Bknd's API proxy supports standard HTTP caching:

# Client can use conditional requests
curl -H "If-None-Match: \"abc123\"" \
  http://localhost:7654/​api/​media/​file/​image.png

# Returns 304 Not Modified if unchanged

Access Control

Public Files (No Auth)

Configure default role with media.read permission:

export default defineConfig({
  auth: {
    guard: {
      roles: {
        anonymous: {
          is_default: true,
          permissions: {
            "media.read": true,  // Public read access
          },
        },
      },
    },
  },
});

Private Files (Auth Required)

Remove media.read from anonymous:

export default defineConfig({
  auth: {
    guard: {
      roles: {
        user: {
          permissions: {
            "media.read": true,
            "media.create": true,
          },
        },
        // No anonymous role, or no media.read permission
      },
    },
  },
});

Access requires auth:

# Fails without auth
curl http://localhost:7654/​api/​media/​file/​private.pdf
# 401 Unauthorized

# Works with auth
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:7654/​api/​media/​file/​private.pdf

Signed URLs (Time-Limited Access)

For S3/​R2, generate presigned URLs:

// Custom endpoint for signed URLs (advanced)
// Requires S3 SDK directly, not through Bknd adapter
import { S3Client, GetObjectCommand } from "@aws-sdk/​client-s3";
import { getSignedUrl } from "@aws-sdk/​s3-request-presigner";

async function getSignedDownloadUrl(filename: string): Promise<string> {
  const client = new S3Client({ /​* config */ });
  const command = new GetObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: filename,
  });
  return getSignedUrl(client, command, { expiresIn: 3600 }); // 1 hour
}

REST API Reference

MethodEndpointDescription
GET/​api/​media/​file/:filenameDownload/​view file
GET/​api/​media/​filesList all files

Request Headers

HeaderDescription
AuthorizationBearer token (if auth required)
If-None-MatchETag for conditional request
RangeByte range for partial download

Response Headers

HeaderDescription
Content-TypeFile MIME type
Content-LengthFile size in bytes
ETagFile hash for caching
Accept-RangesIndicates range support

Common Pitfalls

404 File Not Found

Problem: File URL returns 404.

Causes:

  1. Filename doesn't exist
  2. Wrong path/​case sensitivity
  3. File was deleted

Fix: Verify file exists:

const { data: files } = await api.media.listFiles();
const exists = files.some(f => f.key === filename);

CORS Errors on Direct S3 URL

Problem: Browser blocks direct S3 access.

Fix: Configure CORS on S3 bucket:

{
  "CORSRules": [{
    "AllowedOrigins": ["https://yourapp.com"],
    "AllowedMethods": ["GET"],
    "AllowedHeaders": ["*"],
    "MaxAgeSeconds": 3600
  }]
}

Slow File Serving

Problem: Files load slowly via API proxy.

Fix: Use direct storage URLs with CDN:

// Instead of API proxy
const slow = "/​api/​media/​file/​image.png";

// Use direct CDN URL
const fast = "https://cdn.yourapp.com/​image.png";

Mixed Content (HTTP/​HTTPS)

Problem: HTTPS page loading HTTP file URLs.

Fix: Ensure storage URL uses HTTPS:

// WRONG
url: "http://bucket.s3.amazonaws.com",

// CORRECT
url: "https://bucket.s3.amazonaws.com",

Large File Download Fails

Problem: Download times out or memory error.

Fix: Use streaming for large files:

// Stream instead of loading into memory
const stream = await api.media.getFileStream("large-file.zip");

// Or direct download link
const downloadUrl = `${api.host}/​api/​media/​file/​large-file.zip`;
window.location.href = downloadUrl;

Cloudinary File Not Found

Problem: Cloudinary returns 404 for uploaded file.

Cause: Cloudinary uses eventual consistency; file not yet indexed.

Fix: Wait briefly or use upload response URL directly:

const { data } = await api.media.upload(file);
// Use data.name immediately rather than re-fetching

Verification

Test file serving setup:

async function testFileServing() {
  const filename = "test-image.png";

  // 1. Verify file exists
  const { data: files } = await api.media.listFiles();
  const file = files.find(f => f.key === filename);
  console.log("File exists:", !!file);

  // 2. Test API proxy
  const apiUrl = `${api.host}/​api/​media/​file/​${filename}`;
  const apiRes = await fetch(apiUrl);
  console.log("API proxy status:", apiRes.status);
  console.log("Content-Type:", apiRes.headers.get("content-type"));

  // 3. Test conditional request
  const etag = apiRes.headers.get("etag");
  if (etag) {
    const conditionalRes = await fetch(apiUrl, {
      headers: { "If-None-Match": etag },
    });
    console.log("Conditional request:", conditionalRes.status === 304 ? "304 (cached)" : conditionalRes.status);
  }

  // 4. Test SDK download
  const downloadedFile = await api.media.download(filename);
  console.log("SDK download:", downloadedFile.name, downloadedFile.size);
}

DOs and DON'Ts

DO:

  • Use CDN (Cloudinary/​R2/​CloudFront) for public high-traffic files
  • Set proper cache headers for static assets
  • Use API proxy for private/​auth-required files
  • Implement lazy loading for images
  • Use responsive images with srcSet
  • Handle file not found gracefully

DON'T:

  • Expose private files via public S3 URLs
  • Serve large files without streaming
  • Hardcode storage URLs (use config/​env)
  • Forget CORS configuration for direct access
  • Use local adapter in production
  • Skip error handling for missing files

Related Skills

  • bknd-file-upload - Upload files to storage
  • bknd-storage-config - Configure storage backends
  • bknd-assign-permissions - Set media.read permission
  • bknd-public-vs-auth - Configure public vs authenticated access

More skills from NeverSight

devops-deployer logo
NeverSight/skills_feed

devops-deployer

Comprehensive DevOps and deployment workflow that orchestrates infrastructure automation, CI/CD pipelines, container orchestration, and cloud deployment. Handles everything from infrastructure as code and pipeline setup to monitoring, scaling, and disaster recovery.

215 1
View
avnu logo
NeverSight/skills_feed

avnu

Integrate avnu SDK for Starknet DeFi: token swaps with best-price routing, DCA recurring buys, native staking, gasless transactions via paymaster, and market data. Use when building Starknet trading, DEX aggregation, or DeFi automation features.

215 0
View
post-to-x logo
NeverSight/skills_feed

post-to-x

发布内容和文章到 X (Twitter)。支持带图片的普通推文和 X Articles(长文)。使用真实的 Chrome 和 CDP 来绕过自动化检测。

215 0
View
ralph-prompt-builder logo
NeverSight/skills_feed

ralph-prompt-builder

Master orchestrator for generating Ralph Wiggum-compatible prompts. Analyzes task requirements and routes to appropriate generator (single-task, multi-task, project, or research). Use when you need to create any Ralph loop prompt and want automatic selection of the right generator.

215 0
View
branch-comparison logo
NeverSight/skills_feed

branch-comparison

Use when the user asks to compare branches, analyze git diffs, review changes between branches, update specifications based on code changes, or analyze what changed. Triggers on keywords like "compare branches", "git diff", "what changed", "branch comparison", "code changes", "spec update".

215 0
View
fastapi-development logo
NeverSight/skills_feed

fastapi-development

Build high-performance FastAPI applications with async routes, validation, dependency injection, security, and automatic API documentation. Use when developing modern Python APIs with async support, automatic OpenAPI documentation, and high performance requirements.

215 0
View
zendesk-chat logo
NeverSight/skills_feed

zendesk-chat

Enables Claude to manage Zendesk Chat conversations, support tickets, and customer service operations

215 0
View
forge logo
NeverSight/skills_feed

forge

Mine transcripts for knowledge - decisions, learnings, failures, patterns. Triggers: "forge insights", "mine transcripts", "extract knowledge".

215 0
View
mixpanel logo
NeverSight/skills_feed

mixpanel

Track product analytics and user behavior with Mixpanel's event-based platform.

215 0
View
ocpp logo
NeverSight/skills_feed

ocpp

OCPP protocol reference for EV charging infrastructure development. Covers OCPP 2.0.1 and OCPP 1.6J. Use when working with OCPP messages, charging station code, CSMS/Central System backends, smart charging, transaction handling, or EV charging protocols. Activates on keywords: OCPP, charging stat...

215 0
View
bee-collect logo
NeverSight/skills_feed

bee-collect

Gather scattered ideas into organized GitHub issues. The bee collects pollen from every flower, checks if the hive already has it, and carefully deposits new work in the comb for later. Never edits code—only organizes the work ahead. Use when creating issues from TODOs or planning work.

215 0
View
vercel-react-native-skills logo
NeverSight/skills_feed

vercel-react-native-skills

Comprehensive best practices for React Native and Expo applications. Contains rules across multiple categories covering performance, animations, UI patterns, and platform-specific optimizations.

215 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View