Skip to content

Serving files

Serving files with ConvexFS is done via two steps:

  1. You write a normal Convex query that uses fs.stat to get the file metadata, including the blobId. This is used to construct a URL that points to the component’s blob HTTP endpoint. In our examples, this is mounted at /fs/blobs/{blobId}.
  2. Use this URL in your frontend to download or render the file. You can use it in <img> tags or any other HTML element that supports the src attribute.
import { query } from "./_generated/server";
import { fs } from "./fs";
export const getFilePath = query({
args: { path: v.string() },
handler: async (ctx, args) => {
const siteRoot = process.env.CONVEX_SITE_URL;
const file = await fs.stat(ctx, args.path);
if (!file) {
return null;
}
return `${siteRoot}/fs/blobs/${file.blobId}`;
},
});
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
function Image({ path }: { path: string }) {
const url = useQuery(api.files.getFilePath, { path });
return <img src={url} alt={path} />;
}

…but doesn’t a CDN serve the file contents?

Section titled “…but doesn’t a CDN serve the file contents?”

Yes! Perhaps surprisingly, the ConvexFS blob endpoint does not actually serve the file contents. Instead, it returns a 302 redirect to the appropriate signed CDN URL. Then the file contents are served from bunny.net’s global CDN, which is optimized for fast delivery to users all over the world.

Additionally, the cache max-age timings are coordinated between the component’s 302 response and the CDN’s token expiration. This means the file’s cached content will continue to be used without asking the Convex deployment to regenerate URLs over and over again. But the browser will come back to Convex before the CDN URL becomes invalid.

Personal tokens, but globally cached values.

Section titled “Personal tokens, but globally cached values.”

Every CDN token generated by the component is one-time use. This means that if you revoke access to the resource for a particular user, no more tokens will be issued when their one-time use token expires.

However, while the CDN URL is invalid, the cached blob is not. bunny.net’s CDN does not include the token in the CDN cache key. So the blob will continue to be in a high-performance edge cache for those users who continue to have access.

In other words, authorization is fine grained, but the cache is shared. This provides a great balance between security and performance.

We know how to get files into ConvexFS, and how to serve them to our apps.

But what else can we do to evolve our file system over time?