Integrate Backblaze B2 with TinaCMS media library
As part of an experiment to migrate my website to TinaCMS away from Sanity, I decided I wanted to move my media files to Backblaze B2 buckets. This, along with migrating my content back to markdown, allows for more control over my files, easier backups, and doesn’t lock it behind a proprietary format. Working with the media library was my main hiccup with TinaCMS, almost causing me to give up. But, with a bit more persistence (and digging through Tina’s S3 integration adapter source code) I figured out a relatively straightforward solution to provide the functionality I wanted.
TinaCMS by default supports locating media alongside your content files in a git repo, but I decided against this because I regularly post photos to my website and already have several hundred. Git isn’t optimized to handle files like this. An external media storage service (like Amazon S3, Cloudflare R2, etc) will reduce storage costs and give more control over file versioning (or avoidance of said file versioning).
After struggling for a while, I decided to use a Vercel Functions route instead (this website is currently deployed on Vercel). TinaCMS has an excellent example of how to create a custom API function in a NextJS-centric way, but NextJS and Astro API routes (this website is built on Astro) use different functional arguments and return values, because under the hood one uses Express.js syntax, and the other uses plain Node syntax. These differences, both in syntax and functionality, made integrating TinaCMS’ media library API difficult, so Vercel Functions was the best alternative.
The first step to set up this media library is the easiest, let’s tell TinaCMS that we’re going to use an S3-compatible data store. This guide assumes you already have a functioning TinaCMS installation. If not, see their setup docs.
Add TinaCMS media store config
Install the packages @tinacms/auth
and next-tinacms-s3
using your favorite package manager. Mine is pnpm:
pnpm install @tinacms/auth next-tinacms-s3
In the tina/config.ts
, add (or replace if it already exists), the media
key as follows:
...
media: {
loadCustomStore: async () => {
const pack = await import('next-tinacms-s3');
return pack.TinaCloudS3MediaStore;
},
},
...
That’s it for the TinaCMS config! A painless change. We’ve still got some work to do, though, as it will error out if you try to browse the media library now.
Setup B2 and API token
We need a few key pieces of information from Backblaze B2. If you haven’t already, create an account and a B2 bucket that you’d like to use for your media library. Take note of a few pieces of information:
- The name of the bucket you created (example,
tinacms-media-library
). - The bucket’s “Endpoint” (looks something like
s3.us-west-000.backblazeb2.com
). - Then create a new API key in the Application Keys section. Allow the key both read and write access to your new bucket.
- Save both the
keyID
and theapplicationKey
for later.
- Save both the
Create Vercel API route
For this next step, let’s assume you are also deploying your application on Vercel. If not, TinaCMS docs have examples for other providers. Let’s set up the Vercel API route:
- In the root of your project, create an
api
directory, and then next another directory,s3
, inside of it. - Inside the
s3
directory, create a file called[...media].mjs
- Paste these contents in the file:
import { createMediaHandler } from 'next-tinacms-s3/dist/handlers.js';
import tinaAuth from '@tinacms/auth';
const { isAuthorized } = tinaAuth;
export default createMediaHandler({
config: {
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY || '',
secretAccessKey: process.env.S3_SECRET_KEY || '',
},
region: ##INSERT-REGION-HERE##,
endpoint: ##INSERT-ENDPOINT-HERE##,
},
bucket: ##INSERT-BUCKET-NAME-HERE##,
authorized: async (req, _res) => {
try {
if (process.env.NODE_ENV == 'development') {
return true;
}
const user = await isAuthorized(req);
return user && user.verified;
} catch (e) {
console.error(e);
return false;
}
},
});
Most of this file replicates what TinaCMS gives us, but adds the endpoint
key under the config
object that allows us to use Backblaze B2 in place of Amazon S3.
When filling out the file above:
- Set
bucket
to the name of the bucket you created (example,tinacms-media-library
). - Set
endpoint
to the “Endpoint” value you recorded from your B2 bucket (example,s3.us-west-000.backblazeb2.com
). - Set
region
to the subdomain of your “Endpoint”. For example, if your endpoint iss3.us-west-000.backblazeb2.com
then set the region tous-west-000
.
The above configuration pattern, specifically the endpoint
value, allow use of any other S3-compatible storage service, though I only tested with Backblaze B2.
Finalize setup in Vercel
Your project should be created in Vercel already (docs). We need to add two environment variables (environment variable docs):
S3_ACCESS_KEY
: set this to thekeyID
you saved from the B2 API key setupS3_SECRET_KEY
: set this to theapplicationKey
from the B2 API key
Your application should be ready to deploy now! Wait for Vercel to deploy, and then navigate to the media library in your TinaCMS instance. It should be able to read and upload files to Backblaze B2!