Add video upload and playback to React (Vite)
Add video upload and playback to React (Vite)
Use FastPix SDKs to add resumable uploads and adaptive-bitrate playback to a React app built with Vite.
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 server.
A Vite application is entirely client-side, so it cannot safely store your FastPix Secret Key or receive webhooks. This guide uses a small Express server for those tasks, but you can use any backend you control.
If you’d rather not use React components, 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 Express server creates a signed upload URL using the FastPix Node SDK.
- The React 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:
- A React 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. See get your Access Token ID and Secret Key.
- A React 18 or later application created with Vite.
- Node.js.
- A backend (such as Express) to create upload URLs and receive webhooks.
Install
From your project directory:
The first line covers the browser: the React Uploader is the upload UI and the Web Player plays the result. The second covers the server, where the Node SDK talks to the FastPix API.
Put your credentials in .env:
WARNING:
Do not prefix these withVITE_. Vite inlines everyVITE_*variable into the client bundle, which would publish your Secret Key to every visitor. Only your server reads these.
Import the uploader stylesheet once, in src/main.tsx:
Without it the components render as an unstyled skeleton and you supply the entire look yourself. With it you get the default drop zone, progress track, status text, and buttons, which you can then adjust through the appearance prop or the --fastpix-* CSS variables, both documented in the uploader README.
Create an upload URL
Signed upload URLs are created with your Secret Key, so this belongs on the server:
server.js
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. See upload videos from device.
Point Vite’s dev server at it so /api calls work from the browser without CORS setup:
The proxy applies to vite dev only. Vite builds to static files, so in production serve the API from the same origin as those assets, or enable CORS on it.
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
Give <FastPixUploader> a function instead of a URL. It runs when a file is picked, so a URL is minted per upload rather than per page load:
src/Upload.tsx
src/Upload.jsx
That’s a working uploader: drag and drop, a file picker, progress, and pause, resume, and cancel controls.
You can drag and drop a video, and the upload starts with visible progress.
Build your own layout instead
Pass the sub-components as children and arrange them yourself. Each reads upload state from the parent through context, 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.
Tracking progress yourself, driving the uploader from a ref, reading live state with useUploaderContext(), and building a fully headless UI are covered in the uploader README.
Respond to upload events
The uploader reports its lifecycle through callback props: onProgress, onSuccess, onError, and more. All are optional:
onSuccess fires when the bytes finish uploading. The media still has to be encoded before it can play, which is what the next section covers. The full callback list is in the uploader README.
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 on the same server:
server.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. Capture the raw body withexpress.raw({ type: "application/json" })and verify those exact bytes. 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 server through a tunnel or check the status directly:
Play the video
<fastpix-player> is a web component, so it works in React as a plain tag:
src/Player.tsx
src/Player.jsx
Give it a size, since it fills whatever container it sits in:
Expose the playback ID your webhook stored, so the browser can read it without your credentials:
server.js
Then fetch it and render the player:
src/Watch.tsx
src/Watch.jsx
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
TS2882: Cannot find module or type declarations for side-effect import of '@fastpix/fp-react-uploader/styles.css'
TypeScript has no declarations for CSS imports. Add Vite’s client types to tsconfig.app.json:
Older templates use src/vite-env.d.ts containing /// <reference types="vite/client" /> instead. If that file was deleted, restore it. The same error appears for your own ./index.css, which is a quick way to confirm the cause. Note that vite build alone doesn’t typecheck; the error only surfaces through npm run build, which runs tsc -b 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 callback on the uploader reads stale state.
Callbacks such as onSuccess are captured when the upload starts, so anything they read reflects that moment. Have the callback set a flag, and react to it from an effect that reads current state.
/api requests 404 in dev.
The Vite proxy is missing, or your API server isn’t running. Both vite dev and node server.js need to be running.
/api requests 404 in production.
vite build emits static files only, so anything mounted on Vite’s dev server is gone. Deploy the API separately and serve it from the same origin, or enable CORS on it.
Chunk size is rejected.
chunkSize is in KB, between 5120 and 512000, in multiples of 256.