Serving files
Serving files with ConvexFS is done via two steps:
- You write a normal Convex query that uses
fs.statto get the file metadata, including theblobId. 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}. - 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 thesrcattribute.
Example getFilePath query
Section titled “Example getFilePath query”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}`; },});Example Image component
Section titled “Example Image component”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.
What’s next
Section titled “What’s next”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?