Add video upload and playback to Astro
Use FastPix SDKs to add resumable uploads and adaptive-bitrate playback to an Astro application.
Uploading video is different from uploading other files. Large uploads need to resume after interruptions, and media must be processed into streaming formats before they can be played reliably across devices.
FastPix handles uploads, processing, and adaptive streaming. Your application only needs to create a signed upload URL on the server and render a player. Uploads go directly from the browser to FastPix, so video data never passes through your Astro server.
This guide uses the FastPix Astro Uploader, which is an Astro native component, so no React or other UI framework is required. If you’d rather work at a lower level, the quickstart covers the same workflow using the API directly, and resumable uploads for web covers the underlying @fastpix/resumable-uploads engine.
How it works
The upload workflow is straightforward:
- Your Astro server creates a signed upload URL using the FastPix Node SDK.
- The Astro Uploader uploads the selected file directly to FastPix using the signed URL.
- FastPix processes the uploaded media asynchronously.
- FastPix sends a
video.media.readywebhook when the media is ready for playback. - Your application retrieves the playback ID and plays the video using the FastPix Player.
What you’ll build
By the end of this guide, you’ll have:
- An Astro application that uploads videos directly to FastPix.
- Resumable uploads with upload progress and retry support.
- A FastPix Player that streams the uploaded video.
- A workflow that waits for media processing before playback.
Before you begin
- A FastPix account with an Access Token ID and Secret Key.
- An Astro project v7.
- Node.js.
Install
From your project directory:
The Node SDK talks to the FastPix API from your server, the Astro Uploader provides the upload interface, and the FastPix Player plays uploaded videos.
Configure Astro
Creating upload URLs and retrieving media happen on the server, so Astro must run in server mode instead of generating a fully static site.
Before configuring Astro, install the adapter that matches your deployment environment. This guide uses @astrojs/node for local development; see the full list of supported adapters if you’re deploying elsewhere:
Update astro.config.mjs:
Excluding @fastpix/fp-astro-uploader from dependency optimization ensures the uploader’s client runtime is loaded correctly.
Add your credentials to .env:
WARNING: Do not prefix these with
PUBLIC_. Astro exposes everyPUBLIC_*variable to the browser, which would publish your Secret Key.
Create the SDK client in its own module:
Create an upload URL
Signed upload URLs are created with your Secret Key, so this belongs on the server. An API route gives you an endpoint the browser can call:
src/pages/api/upload-url.ts
src/pages/api/upload-url.js
export const prerender = false is required. Without it Astro tries to run this route at build time, when there’s no request to respond to.
NOTE:
X-Client-Type: web-browsertells FastPix that a browser will perform the upload, so the signed URL is issued for browser use. Send it whenever the file is uploaded from a browser, including this setup, where your server requests the URL and the browser uploads to it.Leave it out when your own server, a CLI, or a native Android or iOS app uploads the bytes.
Keep the mediaId. It identifies the media everywhere else in the API, and you’ll use it later to play the video. Full options are in upload videos from device, or use create media from a URL to import video you already host.
Add the uploader
Render <FastPixUploader> and give it a function that mints a URL when a file is picked:
src/pages/upload.astro
That’s a working uploader: drag and drop, a file picker, progress, and pause, resume, and cancel controls. Styles ship with the component. There’s no stylesheet to import.
You can select or drag a video into the uploader and the upload begins with visible progress.
The resolver is assigned from a <script> rather than passed as a prop because props are serialized when Astro renders on the server, and functions don’t serialize. getUploader waits for the element to be defined and upgraded, then hands you the element itself; endpoint is a property on it.
Build your own layout instead
Pass the sub-components as children and arrange them yourself. Each reads upload state from the host, so placement and order are yours:
autoStart={false} holds the file until someone presses start, which is why a start button appears here and not in the default layout.
Respond to upload events
The uploader emits DOM events on the element getUploader returns: fastpix-upload-start, fastpix-progress, fastpix-success, fastpix-error, and more:
fastpix-success fires when the bytes finish uploading. The media still has to be encoded before it can play, which is what the next section covers.
Wait for the media to be ready
A finished upload isn’t a playable video yet. It still has to be encoded. FastPix sends a video.media.ready webhook once playback is available.
Register your endpoint under Org Settings > Webhooks see Set up webhooks, then handle the event in another API route:
src/pages/api/fastpix/webhook.ts
src/pages/api/fastpix/webhook.js
WARNING:
Verify the signature before trusting a payload. Your endpoint is a public URL, so anyone who finds it could claim a video is ready. Check theFastPix-Signatureheader, an HMAC-SHA256 of the raw body, as described in Set up webhooks. Read the body withrequest.text()and verify that exact string. A parsed and re-serialized body produces different bytes and never matches.
Other events, including video.media.failed, are in the webhook event reference.
Webhooks can’t reach localhost, so while developing either expose your dev server through a tunnel or check the status directly:
Play the video
Fetch the media in the page frontmatter. That runs on the server, so your credentials stay there and only the playback ID reaches the browser. <fastpix-player> is a web component, so it goes straight into your markup:
src/pages/play/[mediaId].astro (TypeScript)
src/pages/play/[mediaId].astro (JavaScript)
The player import lives in a <script>, which Astro only runs in the browser. It registers a custom element and would fail during server rendering.
Give it a size, since it fills whatever container it sits in:
A playback ID isn’t the same as a media ID. One media asset can carry several playback IDs with different access policies. This guide uses accessPolicy: "public", so the ID alone is enough to play the video. Private and DRM playback need a signed token, covered in play uploaded videos along with autoplay, captions, and the player’s full attribute and event surface.
Troubleshooting
Cannot use server-rendered pages without an adapter.
API routes and pages with export const prerender = false run on demand. Install an adapter such as @astrojs/node and set it in astro.config.mjs.
getUploader: the matched element is not an <fastpix-uploader>.
Your script and the component are using different copies of the client runtime. Add optimizeDeps: { exclude: ['@fastpix/fp-astro-uploader'] } to the vite section of astro.config.mjs.
Invalid config: endpoint must be a non-empty string or a function.
The resolver was never assigned, usually because the getUploader call above it threw. Check the browser console for that error first.
Uploads fail with “error code 0” after 5 attempts, but the video still processes.
The signed URL was created without the X-Client-Type: web-browser header, so it isn’t valid for uploads started from a browser. Add it to your upload call as shown above. The rule is about who uploads the bytes, not who requests the URL. A browser doing the upload needs the header even though your server is the one asking for the URL.
A POST to an API route returns 403 from curl but works in the browser.
Astro checks the Origin header on form-style requests. A browser sends it automatically; add -H "Origin: http://localhost:4321" when testing by hand.
Chunk size is rejected.
chunkSize is in KB, between 5120 and 512000, in multiples of 256.