The short version
Key takeaways
- A 60 FPS file is not proof of 60 distinct captured frames. Motion, audio and timing have to survive the whole recording pipeline.
- Let the page say when to start and stop recording. Waiting a fixed number of seconds will break eventually.
- A render that returns a file can still be wrong. Count the dropped frames and hand them back with the video.
What recording a webpage actually involves
A browser is already a good drawing tool. CSS animates, Canvas draws frame by frame, WebGL handles 3D scenes and the Web Audio API makes sound. If you can build a webpage, you can build the contents of a video: an explainer, a data visualisation, a product demo, a channel that publishes on its own.
Loading the page is the easy half. The hard half is capturing what you see without changing how the browser draws it. The GPU has to stay involved, the frame rate has to stay steady, the audio has to stay in sync, and something has to know when the page is genuinely ready.
It also helps to notice that two different jobs share the same search term. Recording someone clicking around a site is screen capture. Rendering a page that was built to be a video is closer to running a render farm. Pagecorder does the second one.
Why a screencast was not enough
The older Puppeteer screencast path shows the gap between recording a browser test and producing a video. It receives PNG frames through the Chrome DevTools Protocol, decodes their base64 transport in Node.js and pipes them into a separate FFmpeg process. That recorder explicitly disables audio and selects software VP9 encoding. A GPU-rendered page does not turn that image-transfer pipeline into a zero-copy, hardware-accelerated recorder.
Its FPS setting controls the output timeline. The recorder repeats captured images to fill the intervals between arriving frames. A file labelled 60 FPS can therefore contain fewer than 60 distinct captures per second: repeating an image cannot recover motion that was missed. Recording within a page with MediaRecorder or canvas.captureStream is another approach, but combining ordinary HTML, multiple canvases and sound still needs its own capture design.
Pagecorder was built around the complete recording job: GPU rendering, native screen and audio capture at 60 FPS, and an MP4 delivered through an API. The page controls when recording starts and stops. Jobs can use a bundled page and assets, and the result includes dropped-frame information, HTTP errors and console logs. Those are recording requirements, not extras that changing an output format supplies.
- Capture the motion, not merely a file with the requested frame-rate metadata.
- Keep browser rendering, video encoding and audio synchronisation working together.
- Report missing frames and page failures alongside the finished video.
Puppeteer has since added a separate Page.record API with audio options. The limitations above describe its older PNG-to-FFmpeg screencast path, not every current recording method. An API option alone does not establish equivalent capture quality or performance.
Let the page decide when to start
A recorder cannot guess when your animation is ready. The network going quiet does not mean the fonts have settled, the data has finished loading in a worker, or the animation has begun. Starting five seconds after the page loads does not fix that. It moves the problem somewhere less obvious.
Pagecorder gives the page a small hook to start and stop recording when it is ready. The optional calls below are skipped when that hook is absent in a normal browser. Here, runAnimation stands for your own animation function, which returns a promise when it finishes.
<script>
window.pagecorder?.('start');
runAnimation().then(() => {
window.pagecorder?.('stop');
});
</script>Wait for the things a viewer would actually notice, not just for the page to finish loading.
At 60 frames per second you get 16 milliseconds
Sixty frames per second means each frame has about 16.7 milliseconds to happen. Layout, your JavaScript, painting, GPU work, reading the frame back out and encoding it all have to fit in that window. A page that feels perfectly smooth on your laptop can still drop frames once it is being rendered at 4K with an encoder running alongside it.
The usual advice applies. Animate transforms and opacity rather than anything that forces layout, keep work out of the frame loop, and profile heavy Canvas or WebGL scenes at the size you will actually export. But the recorder has a second job: noticing what went wrong. A request that returns a file is not a success if fifty frames went missing along the way.
So Pagecorder hands back the timings of any dropped frames, the failed requests and the browser console alongside the MP4. It turns “the video stutters” into “something went wrong at 14 seconds, and here is what the page was complaining about”.
Keep the browser and the encoder close together
The whole thing started from a gap. Taking screenshots with browser automation was easy; moving thousands of high-resolution frames between processes fast enough to encode them was not. The version that runs today uses Chromium with the GPU switched on and talks to FFmpeg’s libraries directly from C++ instead of shelling out to the command-line tool.
The idea behind that is simply to stop copying. Every time a frame moves from the graphics card into main memory, across a process boundary and into an encoder, it spends time being carried rather than compressed. Keeping capture and encoding close leaves more of those 16 milliseconds for the page itself.
Audio brings a second clock into the picture. The frames and the samples have to agree on a timeline, survive whatever buffering the encoder does and finish together. A pipeline that only handles video looks perfect in a five-second test and drifts badly over a ten-minute render.
Send the whole page, not just a link
Pointing the renderer at a public URL is convenient, but it makes the output depend on DNS, CDN speed, rate limits and whichever version of the site happens to be deployed that day. A font that fails to load or an image that arrives late changes both how the video looks and how long it takes.
Pagecorder also accepts a ZIP file containing the page and everything it needs, which gets served locally for the duration of the job. Most of the network variability disappears, an old render can be repeated years later, and you end up with a file that records exactly what was rendered rather than where the renderer was told to look.
A plain URL is still the right choice for a one-off. For anything you produce repeatedly, the bundle behaves more like a source file.
Build it or pay for it
Building this yourself means owning the capture pipeline, not just calling a browser API. You need to test distinct captured frames, audio synchronisation, GPU behaviour and output quality at the resolution and duration you will actually use. Then there are browser upgrades, encoder changes and failures that only appear under load.
Pagecorder puts that work behind a rendering API. Send a URL or a ZIP archive, let the page signal its start and finish, and retrieve the MP4 with the recording diagnostics. It is for products that need to generate video without building and operating their own browser-and-encoder service.
I cannot name Pagecorder’s customers, but the patterns repeat: automated YouTube channels, social clips, product demos, dashboards that turn into videos, motion graphics built by people who already know CSS. All of them get to compose in a browser and treat the rendering as somebody else’s infrastructure.


