How to optimize CMS images in batches with Apify
An image optimization task sounds simple until you have a few hundred files, several output formats, and a content team waiting to publish. A smaller file helps only if it still looks right, keeps the dimensions your page needs, and has a reliable destination in your media library. When managing growing content libraries or building an automated SEO pipeline, unoptimized media assets quickly become a primary bottleneck for web performance.
This article walks you through using an Apify Actor, Sqoosh Image Compressor, to turn a list of source images into lightweight, modern web formats in batches. The Actor was originally built upon the open-source Squoosh project by Google Chrome Labs - hence its name on the Apify Store. The current release uses sharp and libvips for encoding under the hood. While the Actor preserves its existing Store name and common compatibility input keys, it no longer depends on legacy client-side Squoosh workers.
In this guide, you will prepare a small sample list, request JPEG and WebP outputs, inspect the generated records and artifacts, and create a review mapping ready for a safe CMS import. The workflow stops at a reviewable output stage; replacing production CMS assets remains a deliberate, separate step requiring your CMS credentials and an approved mapping.
!How to optimize CMS images in batches with Apify
What the workflow produces
Each successful source-image and output-codec pair produces two distinct outputs: a structured record in the run's default dataset and a downloadable binary file in the run's key-value store. For example, requesting both JPEG and WebP for a single image produces two dataset rows and two stored files. If a conversion fails, the Actor logs the error and excludes it from the successful outputs.
The primary dataset fields describe the source URL, selected codec, output key, original byte size, and encoded byte size. Always inspect the actual dataset before feeding output keys into downstream automation.
The compatibility key mozjpeg encodes standard JPEG files through sharp; it does not invoke the legacy MozJPEG WebAssembly binary. Currently supported encoder keys are mozjpeg, webp, avif, and oxipng (which produces optimized PNGs). This release does not support formats such as JXL and WP2, and it fails explicitly rather than silently falling back to another format.
Output files are stored as run artifacts. Download them or transfer them to permanent cloud storage before your Apify storage retention period expires. An Apify artifact URL is designed for inspection and data transfer, so avoid using it as a permanent CMS media link.
Step 1: Choose a representative image sample
Start with images that you own or have permission to process. A useful first sample includes a photograph, a transparent graphic, an image with small text, and an unusually wide or tall banner. If you manage an e-commerce product catalog, include both a high-resolution product detail image and a thumbnail. Testing distinct asset types helps you quickly expose format-specific artifacts and quality issues.
Make sure to provide direct, publicly accessible image URLs. A web page containing an embedded image is not the same as the raw image file. Similar to collecting source links in batch media workflows, URLs that require an active browser login session, an expired signed token, or access to a private network will fail to download.
Maintain a simple source inventory with an internal asset ID, original URL, intended page role, current pixel dimensions, and whether transparency must be preserved. Add a dedicated column for the CMS asset ID if you plan to import results back into your database. Never rely on filenames alone to match assets, as different folders across large sites frequently share identical filenames.
For your initial run, stick to two or three images. A small, bounded test proves your input and transfer path without burning compute or leaving you with hundreds of unreviewed conversions.
Step 2: Configure the image conversion
Open Sqoosh Image Compressor in the Apify Console and configure a bounded JSON input. Click on the "Try for free" button
!Sqoosh Image Compressor
Here is a minimal configuration requesting JPEG and WebP outputs for a single public test image:
{
"images": [
"https://httpbin.org/image/png"
],
"encode": {
"mozjpeg": {
"quality": 75
},
"webp": {
"quality": 75
}
}
}
!Sqoosh Image Compressor - Inpu ton Apify
Replace the test URL with an image from your own inventory for your acceptance check. Public test endpoints can change or go offline, so do not rely on them for production work.
The quality setting of 75 serves as a practical baseline for evaluation, not a universal rule. Quality values are not directly comparable across different codecs. A photograph often looks clean at 75, whereas a software dashboard screenshot or a graphic with sharp typography may show visible compression artifacts at that same setting.
The Actor enforces runtime safety bounds on image count, total downloaded bytes, pixel dimensions, output file sizes, and conversion duration. These boundaries prevent oversized images or runaway batches from exhausting resources. Review the input schema and documentation for exact limits before scaling up, and avoid bypassing limits without checking your source image dimensions.
Step 3: Run a small batch and inspect the evidence
Run the Actor and wait for it to finish. Once complete, inspect both the dataset and the key-value store. A green "Succeeded" status indicates that the container completed its run, but you still need to verify the generated outputs. Confirm that the dataset contains the expected number of successful rows and that the corresponding files exist in storage.
For the single-image input above requesting two codecs, you should see two successful rows: one JPEG and one WebP. If only one row appears, check the run log to see what failed rather than assuming both were processed.
Download and inspect each generated image in an image viewer. Check the entire frame alongside fine details: sharp borders, fine text, gradients, skin tones, and transparent backgrounds. Verify the dimensions as well. An image that opens successfully but loses transparency, rotates unexpectedly, or renders at incorrect dimensions is not ready for production CMS use.
The Actor's build has been validated with local and cloud JPEG and WebP outputs, and automated tests verify decoding for generated JPEG, WebP, AVIF, and PNG files. While this confirms that the encoding pipeline functions as designed, it does not guarantee that every output file will be smaller than its source or meet your team's visual standards.
Below are the actual 100 × 100 JPEG and WebP outputs generated from the test canary run and stored in this repository's assets:
!Actual JPEG output from the private image conversion canary
!Actual WebP output from the private image conversion canary
In this test, the source PNG was converted to a 3,025-byte JPEG and a 2,558-byte WebP file. The repository's provenance record tracks the run ID, build ID, source URL, and SHA-256 hashes for full verification.
Step 4: Measure file-size changes correctly
Always calculate byte savings using the exact source asset as the baseline:
def byte_savings(size_before: int, size_after: int) -> dict:
if size_before <= 0 or size_after <= 0:
raise ValueError("Use positive measured file sizes.")
return {
"bytes_saved": size_before - size_after,
"percent_saved": round(100 * (1 - size_after / size_before), 2),
}
A negative savings value is a completely normal outcome: it simply means the encoded output is larger than the original file. Do not drop these rows or replace negative numbers with zero to make aggregate metrics look better. Tiny icons, already compressed assets, and lossless re-encodings often result in larger files.
Keep visual quality checks separate from raw byte savings. An image that achieves a 70% size reduction but turns product packaging text blurry is unacceptable for production. A milder compression setting or an alternate format is often the better compromise. Also, if your CMS or CDN already performs on-the-fly transformations, verify whether pre-compressing assets delivers meaningful savings or merely introduces an extra layer of lossy compression.
When presenting results to your team, report sample sizes, asset types, output formats, dimensions, total bytes saved, failed inputs, and rejected visual candidates. Avoid extrapolating one dramatic saving across an entire media library. High-performing assets complement broader site optimization, such as maintaining an XML sitemap and efficient crawl structure, but raw image compression alone does not guarantee higher search rankings without measuring real-world site delivery.
Step 5: Export outputs with the Apify API
You can execute the same bounded run programmatically using Python (version 3.11 or later) and the official Apify client:
pip install "apify-client>=3.3,<4"
Store your API token securely in an environment variable rather than hardcoding it in scripts, public notebooks, or client-side bundles.
import os
from datetime import timedelta
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
# Launch a bounded run of the Sqoosh Image Compressor Actor
run = client.actor("eunit/sqoosh-image-compressor").call(
run_input={
"images": ["https://httpbin.org/image/png"],
"encode": {
"mozjpeg": {"quality": 75},
"webp": {"quality": 75},
},
},
run_timeout=timedelta(seconds=120),
max_total_charge_usd=0.10,
)
if run is None:
raise RuntimeError("Actor run was not found.")
if str(run.status) != "SUCCEEDED":
raise RuntimeError(f"Inspect the run before importing outputs: {run.status}")
# Iterate through dataset records containing output metadata
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item)
This script initiates the conversion and outputs the dataset records for verification. Orchestrating Apify Actors with Python mirrors patterns used in monitoring scheduled tasks and scraping workflows. Use the returned output keys and the run's key-value store to download approved assets, keeping your internal asset ID mapped to each record for traceability.
Note that the max_total_charge_usd parameter sets a spending cap per run. Total costs for ongoing batch jobs depend on run frequency, total converted outputs, and active Actor pricing. Check the current Store pricing model before processing large batches.
Step 6: Prepare a CMS import mapping
Create a structured reconciliation table containing one row for every source image and its replacement candidates. Include the CMS asset ID, source URL, chosen format, local output file path, dimensions, byte sizes, and an explicit approval status. If you generated multiple formats (such as WebP alongside JPEG fallback), map each format to its appropriate destination field instead of overwriting a single asset record.
Different CMS architectures handle asset updates differently:
- Some CMS platforms require uploading a new media file to obtain a new asset ID, followed by updating references in existing articles or product records.
- Other platforms allow in-place asset replacements under the same ID.
Keep these operations distinct. Uploading an optimized file does not automatically update pages referencing the old URL. If your workflow involves publishing across content hubs, maintaining structured manifests - similar to synchronizing data in automated CMS and Notion pipelines - prevents broken image links.
Test the update in a staging environment or on unpublished drafts first. Inspect pages across mobile and desktop breakpoints to verify that responsive srcset definitions, aspect ratios, focal points, and alternative text remain intact. Image encoders adjust file bytes, but presentation settings remain the responsibility of your CMS configuration.
Always retain original image files and maintain an explicit rollback mapping linking old asset IDs to new ones. If an automated import is interrupted, this mapping allows you to pinpoint where the process stopped and reverse changes if needed. A failed or missing conversion should never delete an original asset.
Wrapping up
A reliable image optimization pipeline produces verifiable files, maintains a clear link between sources and outputs, and provides a safe rollback mechanism before any CMS database is updated. By starting with a representative sample, verifying visual fidelity alongside byte counts, and scripting batch runs with Apify, you can automate image compression without risking broken layouts or degraded visuals.
To explore the tool, visit Sqoosh Image Compressor on Apify. For additional technical references, check the sharp documentation, Apify key-value store guides, and the Actor's input schema.
5 reactions
Introducing hybrid on-device inference using Firebase AI Logic: Web Architecture & Implementation Guide
Original: codingcat.dev
Calling frontier multimodal models directly from a web or mobile client is a security minefield if you expose raw API keys or hardcode brittle system prompts in your frontend bundle. Following the announcement of Introducing hybrid on-device inference using Firebase AI Logic by Siddharth Gupta, Erik Eldridge on the Official Firebase Blog (2025-06-09), here is our hands-on CodingCat.dev breakdown of what actually shipped, how it works under the hood, and how we wire it into production TypeScript, Next.js, Astro, SvelteKit, React, and Angular applications. The Firebase team announced a powerful new experimental feature in the Firebase AI Logic client SDK for Web: hybrid on-device inference!
The Mental Model: Why Introducing hybrid on-device inference using Firebase AI Logic Matters for Web Devs
As a Google Developer Expert (GDE) for Firebase and Senior Solutions Engineer at CodingCat.dev, I evaluate every Firebase update through one lens: does it reduce boilerplate while keeping our runtime security and cloud bills predictable? Firebase AI Logic lets you invoke Gemini and Imagen models from client and edge apps using the Firebase Web SDK while keeping your API credentials off the wire, enforcing Firebase App Check, and managing server-side prompt templates centrally.
> AJ Pro-Tip from Alex: Combine Firebase AI Logic with server prompt templates, Firebase Remote Config feature flags, and project spend caps so you can iterate on prompts or swap Gemini models instantly without redeploying your Next.js or Astro frontend.
How It Works: Technical Breakdown & Implementation
Harness the power of on-device AI models with a seamless fallback to cloud-hosted models
This ensures your generative AI-powered features are always available to your users, online or offline.
Chrome on Desktop supports Gemini Nano, a powerful on-device model that can be downloaded and hosted in the browser on-demand. Running AI models directly on a user’s device offers some great advantages:
- Enhanced privacy: Sensitive data can stay right on the user’s device.
- Offline availability: Your app’s AI features can work even without an internet connection.
- Cost savings: On-device inference is available at no cost, reducing your application’s operational expenses.
- On-device performance: You can often get high performance thanks to hardware acceleration.
You can build AI features that utilize all these advantages of an on-device model, but then fallback to a cloud-hosted model when its capabilities are needed.
On-Device and In-Cloud Inference
The Firebase AI Logic client SDK for Web has a hybrid on-device extension that uses the proposed W3C Prompt API for on-device model capabilities. It uses built-in, on-device models (like Gemini Nano in Chrome) when it’s available. If that’s not an option on a particular device or browser, the SDK falls back to using Gemini models on the server. This maximizes your reach, ensuring that your users can access your AI features, regardless of their device capabilities.
How it works
Using hybrid on-device inference is straightforward. The Firebase AI Logic client SDK for Web automatically handles the logic of checking for on-device model availability and falling back to a cloud-hosted model if necessary.
Here’s a look at how you can implement hybrid inference in your web app:
// Initialize the Gemini Developer API backend service
const
// Create a `GenerativeModel` instance
// Set the mode, for example to use on-device model when possible
const
// run inference and log result
const
console.
This implementation is almost identical to a regular getGenerativeModel call, but with the addition of the mode parameter that sets how you want to access a model. The SDK takes care of the rest!
Furthermore, you can also configure this code to always use either an on-device or cloud-hosted model for inference by switching the `mode` parameter:
// Run inference only on device. Does not fallback to cloud-hosted models.
const
// Run inference only on the cloud-hosted models.
const
If you’re curious, you can try out our demo **and view the codebase**.
Production Web Architecture & Security Checklist
Whether we are deploying on Firebase App Hosting, Cloudflare Workers, or Vercel, shipping AI features to production requires defense-in-depth across three layers:
- Enforce Firebase App Check & Attestation: **Protect your web endpoints, Cloud Functions, Data Connect GraphQL resolvers, and Firebase AI Logic calls against unauthorized replay or billing abuse using Firebase App Check with reCAPTCHA Enterprise**.
- **Validate Locally with the Firebase Emulator Suite: **Run deterministic integration tests against local Firestore, Auth, Functions, and Data Connect PostgreSQL emulators before promoting schema or rule changes to production.
- **Keep Server & Client Boundaries Type-Safe: **Use the modular Firebase Web SDK and generated TypeScript bindings so SSR frameworks like Next.js, Astro, SvelteKit, and Angular serialize state cleanly without hydration flickers.
Conclusion
Accelerate your full-stack web and AI development by pairing Introducing hybrid on-device inference using Firebase AI Logic with strict Security Rules, Firebase App Check, and local emulator testing. Come tell us on X (@CodingCatDev) and LinkedIn (CodingCat.dev) how you are using Firebase in your stack, and subscribe to the CodingCat.dev YouTube Channel so you don't miss what we build next!
Watch Our Related Firebase Video Tutorial on YouTube
Want to see how we architect and debug Firebase web applications live? Watch our related CodingCat.dev session Firebase SQL with Data Connect below, and make sure to Subscribe to CodingCat.dev on YouTube for weekly full-stack web and AI deep dives:
{% youtube N25khadfUag %}
Resources, Official Docs & Related CodingCat.dev Guides
- Primary Release Source: **Introducing hybrid on-device inference using Firebase AI Logic on the Official Firebase Blog (published 2025-06-09 by Siddharth Gupta, Erik Eldridge)
- **Official Firebase Documentation: **Firebase Developer Docs & Web SDK Reference
- **Related on CodingCat.dev: Transactions Updates for Data Connect
- Related on CodingCat.dev: Fueld.AI's nutrition coach with Firebase and Google Cloud
- Related on CodingCat.dev: Firebase SQL with Data Connect
- Related on CodingCat.dev: Build a Custom No-Code App in 90 Minutes
Frequently Asked Questions (FAQ)
What are the key capabilities introduced in Introducing hybrid on-device inference using Firebase AI Logic?
Introducing hybrid on-device inference using Firebase AI Logic (announced 2025-06-09 by the Firebase team) enhances the platform for Firebase, Google Cloud, Web Dev, AI, TypeScript workloads. The Firebase team announced a powerful new experimental feature in the Firebase AI Logic client SDK for Web: hybrid on-device inference! Developers can review the primary announcement on the Firebase Blog alongside our implementation patterns and production checklist above.
How do web developers use Introducing hybrid on-device inference using Firebase AI Logic with Next.js, SvelteKit, React, or Angular?
Web developers can integrate these features using the modular Firebase JavaScript/TypeScript SDK, Firebase Data Connect web bindings, and Firebase App Hosting. Always pair client-side SDK initialization with Firebase App Check and strict Security Rules, and test locally with the Firebase Local Emulator Suite before deploying.
Where can I watch video tutorials on Firebase Web and AI development?
You can watch our hands-on Firebase video walkthroughs on the CodingCat.dev YouTube Channel, including Firebase SQL with Data Connect, and follow us on X (@CodingCatDev) for weekly full-stack tutorials.
Web Workers in Firefox: Comlink, Transferable Objects, SharedArrayBuffer, and Module Workers
Web Workers allow JavaScript to run in background threads separate from the main thread. This prevents long-running operations from blocking the UI, keeping the page responsive. Firefox has excellent Web Worker support with great DevTools integration. This guide covers Web Workers, Shared Workers, and the Comlink library.
The Problem: The Single-Threaded Main Thread
JavaScript is single-threaded by design. Any expensive computation — sorting a large array, processing CSV data, image manipulation, encryption — blocks the main thread and freezes the UI.
Symptoms of main thread blocking:
- UI doesn't respond to clicks during the operation
- Animations stutter
- DevTools Performance panel shows long yellow "scripting" blocks
- Chrome/Firefox's "Page Unresponsive" dialog appears
Web Workers solve this by moving the computation to a background thread.
Creating a Basic Worker
// main.js (runs in page)
const worker = new Worker('/worker.js');
// Send message to worker
worker.postMessage({ type: 'PROCESS', data: largeArray });
// Receive result from worker
worker.onmessage = (event) => {
const result = event.data;
updateUI(result);
};
worker.onerror = (error) => {
console.error('Worker error:', error.message);
};
// Terminate the worker when done
worker.terminate();
// worker.js (background thread)
self.onmessage = (event) => {
const { type, data } = event.data;
if (type === 'PROCESS') {
// Heavy computation here - doesn't block UI
const result = processData(data);
self.postMessage(result);
}
};
function processData(data) {
// Expensive operation
return data.sort((a, b) => a.value - b.value);
}
Inline Workers with Blob URLs
Create workers without a separate file:
const workerCode = `
self.onmessage = (event) => {
const result = expensiveComputation(event.data);
self.postMessage(result);
};
function expensiveComputation(data) {
return data.map(x => x * 2);
}
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
Transferable Objects (Zero-Copy)
Normally, postMessage() copies data (serialized with structured clone). For large binary data, transfer ownership instead — zero-copy:
const buffer = new ArrayBuffer(1024 * 1024 * 10); // 10MB
// Transfer ownership (buffer becomes empty in main thread)
worker.postMessage({ buffer }, [buffer]);
// buffer.byteLength === 0 now in main thread
// In worker, receive the transferred buffer
self.onmessage = (event) => {
const { buffer } = event.data;
// Process buffer
// Transfer back:
self.postMessage({ buffer }, [buffer]);
};
Transferable types: ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas.
Comlink: Cleaner Worker API
The raw postMessage API is verbose. Comlink wraps workers in a Proxy that makes async calls look like regular function calls:
// worker-api.js
import * as Comlink from 'comlink';
const api = {
async processData(data) {
return data.map(x => x * 2).filter(x => x > 10);
},
async fetchAndProcess(url) {
const response = await fetch(url);
const data = await response.json();
return this.processData(data);
}
};
Comlink.expose(api);
// main.js
import * as Comlink from 'comlink';
const worker = new Worker('./worker-api.js', { type: 'module' });
const api = Comlink.wrap(worker);
// Call worker functions as if they were local async functions
const result = await api.processData([1, 5, 15, 20]);
SharedArrayBuffer and Atomics
For workers that need to share memory (requires COOP/COEP headers):
// Create shared buffer
const shared = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100);
const array = new Int32Array(shared);
// Send to worker (shared, not copied)
worker.postMessage({ shared });
// Atomics for thread-safe operations
Atomics.add(array, 0, 1); // thread-safe increment
Atomics.store(array, 1, 42); // thread-safe write
Atomics.load(array, 1); // thread-safe read
Atomics.wait(array, 0, 0); // block worker until value changes (workers only)
Firefox note: SharedArrayBuffer requires cross-origin isolation (COOP + COEP headers) in Firefox. If you see SharedArrayBuffer is not defined, add these response headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Module Workers
Workers can use ES modules (Firefox 114+):
// ESM worker
const worker = new Worker('/worker.js', { type: 'module' });
// worker.js as ES module
import { processData } from './utils.js';
import * as Comlink from 'comlink';
Comlink.expose({ processData });
Shared Workers
A SharedWorker is shared across all tabs/windows of the same origin:
// Each page connects to the same SharedWorker
const worker = new SharedWorker('/shared-worker.js');
worker.port.start();
worker.port.postMessage('hello');
worker.port.onmessage = (event) => console.log(event.data);
// shared-worker.js
const clients = new Set();
self.onconnect = (event) => {
const port = event.ports[0];
clients.add(port);
port.onmessage = (event) => {
// Broadcast to all connected tabs
clients.forEach(client => client.postMessage(event.data));
};
port.start();
};
Use cases: real-time sync between tabs, shared WebSocket connection, cached data shared across tabs.
Firefox DevTools for Workers
Console: worker console.log() appears in the main page's DevTools console with a "worker" label. Easy to identify.
Debugger: open DevTools → Debugger → the Sources panel shows worker files under "Workers". Set breakpoints directly in worker code. The debugger pauses inside the worker thread.
about:debugging → Workers: shows all running workers and their status.
Performance Measurement
// Benchmark without and with worker
const start = performance.now();
processData(largeArray); // main thread
console.log('Main thread:', performance.now() - start, 'ms');
// With worker:
const startW = performance.now();
worker.postMessage(largeArray);
worker.onmessage = () => {
console.log('Worker:', performance.now() - startW, 'ms');
// Note: includes serialization overhead
};
Workers have message passing overhead — for very small operations, the overhead exceeds the benefit. Workers are valuable for operations >100ms.
Mentions
How to optimize CMS images in batches with Apify An image optimization task sounds simple until you have a few hundred files, several output formats, and a content team waiting to publish. A smaller file helps only if it still looks right, keeps the dimensions your page needs, and has a reliable destination in your media library. When managing growing content libraries or building an automated SEO pipeline, unoptimized media assets quickly become a primary bottleneck for web performance. This article walks you through using an Apify Actor, Sqoosh Image Compressor, to turn a list of source images into lightweight, modern web formats in batches. The Actor was originally built upon the open-source Squoosh project by Google Chrome Labs - hence its name on the Apify Store. The current release uses sharp and libvips for encoding under the hood. While the Actor preserves its existing Store name and common compatibility input keys, it no longer depends on legacy client-side Squoosh workers. In this guide, you will prepare a small sample list, request JPEG and WebP outputs, inspect the generated records and artifacts, and create a review mapping ready for a safe CMS import. The workflow stops at a reviewable output stage; replacing production CMS assets remains a deliberate, separate step requiring your CMS credentials and an approved mapping. !How to optimize CMS images in batches with Apify What the workflow produces Each successful source-image and output-codec pair produces two distinct outputs: a structured record in the run's default dataset and a downloadable binary file in the run's key-value store. For example, requesting both JPEG and WebP for a single image produces two dataset rows and two stored files. If a conversion fails, the Actor logs the error and excludes it from the successful outputs. The primary dataset fields describe the source URL, selected codec, output key, original byte size, and encoded byte size. Always inspect the actual dataset before feeding output keys into downstream automation. The compatibility key mozjpeg encodes standard JPEG files through sharp; it does not invoke the legacy MozJPEG WebAssembly binary. Currently supported encoder keys are mozjpeg, webp, avif, and oxipng (which produces optimized PNGs). This release does not support formats such as JXL and WP2, and it fails explicitly rather than silently falling back to another format. Output files are stored as run artifacts. Download them or transfer them to permanent cloud storage before your Apify storage retention period expires. An Apify artifact URL is designed for inspection and data transfer, so avoid using it as a permanent CMS media link. Step 1: Choose a representative image sample Start with images that you own or have permission to process. A useful first sample includes a photograph, a transparent graphic, an image with small text, and an unusually wide or tall banner. If you manage an e-commerce product catalog, include both a high-resolution product detail image and a thumbnail. Testing distinct asset types helps you quickly expose format-specific artifacts and quality issues. Make sure to provide direct, publicly accessible image URLs. A web page containing an embedded image is not the same as the raw image file. Similar to collecting source links in batch media workflows, URLs that require an active browser login session, an expired signed token, or access to a private network will fail to download. Maintain a simple source inventory with an internal asset ID, original URL, intended page role, current pixel dimensions, and whether transparency must be preserved. Add a dedicated column for the CMS asset ID if you plan to import results back into your database. Never rely on filenames alone to match assets, as different folders across large sites frequently share identical filenames. For your initial run, stick to two or three images. A small, bounded test proves your input and transfer path without burning compute or leaving you with hundreds of unreviewed conversions. Step 2: Configure the image conversion Open Sqoosh Image Compressor in the Apify Console and configure a bounded JSON input. Click on the "Try for free" button !Sqoosh Image Compressor Here is a minimal configuration requesting JPEG and WebP outputs for a single public test image: { "images": [ "https://httpbin.org/image/png" ], "encode": { "mozjpeg": { "quality": 75 }, "webp": { "quality": 75 } } } !Sqoosh Image Compressor - Inpu ton Apify Replace the test URL with an image from your own inventory for your acceptance check. Public test endpoints can change or go offline, so do not rely on them for production work. The quality setting of 75 serves as a practical baseline for evaluation, not a universal rule. Quality values are not directly comparable across different codecs. A photograph often looks clean at 75, whereas a software dashboard screenshot or a graphic with sharp typography may show visible compression artifacts at that same setting. The Actor enforces runtime safety bounds on image count, total downloaded bytes, pixel dimensions, output file sizes, and conversion duration. These boundaries prevent oversized images or runaway batches from exhausting resources. Review the input schema and documentation for exact limits before scaling up, and avoid bypassing limits without checking your source image dimensions. Step 3: Run a small batch and inspect the evidence Run the Actor and wait for it to finish. Once complete, inspect both the dataset and the key-value store. A green "Succeeded" status indicates that the container completed its run, but you still need to verify the generated outputs. Confirm that the dataset contains the expected number of successful rows and that the corresponding files exist in storage. For the single-image input above requesting two codecs, you should see two successful rows: one JPEG and one WebP. If only one row appears, check the run log to see what failed rather than assuming both were processed. Download and inspect each generated image in an image viewer. Check the entire frame alongside fine details: sharp borders, fine text, gradients, skin tones, and transparent backgrounds. Verify the dimensions as well. An image that opens successfully but loses transparency, rotates unexpectedly, or renders at incorrect dimensions is not ready for production CMS use. The Actor's build has been validated with local and cloud JPEG and WebP outputs, and automated tests verify decoding for generated JPEG, WebP, AVIF, and PNG files. While this confirms that the encoding pipeline functions as designed, it does not guarantee that every output file will be smaller than its source or meet your team's visual standards. Below are the actual 100 × 100 JPEG and WebP outputs generated from the test canary run and stored in this repository's assets: !Actual JPEG output from the private image conversion canary !Actual WebP output from the private image conversion canary In this test, the source PNG was converted to a 3,025-byte JPEG and a 2,558-byte WebP file. The repository's provenance record tracks the run ID, build ID, source URL, and SHA-256 hashes for full verification. Step 4: Measure file-size changes correctly Always calculate byte savings using the exact source asset as the baseline: def byte_savings(size_before: int, size_after: int) -> dict: if size_before <= 0 or size_after <= 0: raise ValueError("Use positive measured file sizes.") return { "bytes_saved": size_before - size_after, "percent_saved": round(100 * (1 - size_after / size_before), 2), } A negative savings value is a completely normal outcome: it simply means the encoded output is larger than the original file. Do not drop these rows or replace negative numbers with zero to make aggregate metrics look better. Tiny icons, already compressed assets, and lossless re-encodings often result in larger files. Keep visual quality checks separate from raw byte savings. An image that achieves a 70% size reduction but turns product packaging text blurry is unacceptable for production. A milder compression setting or an alternate format is often the better compromise. Also, if your CMS or CDN already performs on-the-fly transformations, verify whether pre-compressing assets delivers meaningful savings or merely introduces an extra layer of lossy compression. When presenting results to your team, report sample sizes, asset types, output formats, dimensions, total bytes saved, failed inputs, and rejected visual candidates. Avoid extrapolating one dramatic saving across an entire media library. High-performing assets complement broader site optimization, such as maintaining an XML sitemap and efficient crawl structure, but raw image compression alone does not guarantee higher search rankings without measuring real-world site delivery. Step 5: Export outputs with the Apify API You can execute the same bounded run programmatically using Python (version 3.11 or later) and the official Apify client: pip install "apify-client>=3.3,<4" Store your API token securely in an environment variable rather than hardcoding it in scripts, public notebooks, or client-side bundles. import os from datetime import timedelta from apify_client import ApifyClient client = ApifyClient(os.environ["APIFY_TOKEN"]) # Launch a bounded run of the Sqoosh Image Compressor Actor run = client.actor("eunit/sqoosh-image-compressor").call( run_input={ "images": ["https://httpbin.org/image/png"], "encode": { "mozjpeg": {"quality": 75}, "webp": {"quality": 75}, }, }, run_timeout=timedelta(seconds=120), max_total_charge_usd=0.10, ) if run is None: raise RuntimeError("Actor run was not found.") if str(run.status) != "SUCCEEDED": raise RuntimeError(f"Inspect the run before importing outputs: {run.status}") # Iterate through dataset records containing output metadata for item in client.dataset(run.default_dataset_id).iterate_items(): print(item) This script initiates the conversion and outputs the dataset records for verification. Orchestrating Apify Actors with Python mirrors patterns used in monitoring scheduled tasks and scraping workflows. Use the returned output keys and the run's key-value store to download approved assets, keeping your internal asset ID mapped to each record for traceability. Note that the max_total_charge_usd parameter sets a spending cap per run. Total costs for ongoing batch jobs depend on run frequency, total converted outputs, and active Actor pricing. Check the current Store pricing model before processing large batches. Step 6: Prepare a CMS import mapping Create a structured reconciliation table containing one row for every source image and its replacement candidates. Include the CMS asset ID, source URL, chosen format, local output file path, dimensions, byte sizes, and an explicit approval status. If you generated multiple formats (such as WebP alongside JPEG fallback), map each format to its appropriate destination field instead of overwriting a single asset record. Different CMS architectures handle asset updates differently: - Some CMS platforms require uploading a new media file to obtain a new asset ID, followed by updating references in existing articles or product records. - Other platforms allow in-place asset replacements under the same ID. Keep these operations distinct. Uploading an optimized file does not automatically update pages referencing the old URL. If your workflow involves publishing across content hubs, maintaining structured manifests - similar to synchronizing data in automated CMS and Notion pipelines - prevents broken image links. Test the update in a staging environment or on unpublished drafts first. Inspect pages across mobile and desktop breakpoints to verify that responsive srcset definitions, aspect ratios, focal points, and alternative text remain intact. Image encoders adjust file bytes, but presentation settings remain the responsibility of your CMS configuration. Always retain original image files and maintain an explicit rollback mapping linking old asset IDs to new ones. If an automated import is interrupted, this mapping allows you to pinpoint where the process stopped and reverse changes if needed. A failed or missing conversion should never delete an original asset. Wrapping up A reliable image optimization pipeline produces verifiable files, maintains a clear link between sources and outputs, and provides a safe rollback mechanism before any CMS database is updated. By starting with a representative sample, verifying visual fidelity alongside byte counts, and scripting batch runs with Apify, you can automate image compression without risking broken layouts or degraded visuals. To explore the tool, visit Sqoosh Image Compressor on Apify. For additional technical references, check the sharp documentation, Apify key-value store guides, and the Actor's input schema.
Introducing hybrid on-device inference using Firebase AI Logic: Web Architecture & Implementation Guide Original: codingcat.dev Calling frontier multimodal models directly from a web or mobile client is a security minefield if you expose raw API keys or hardcode brittle system prompts in your frontend bundle. Following the announcement of Introducing hybrid on-device inference using Firebase AI Logic by Siddharth Gupta, Erik Eldridge on the Official Firebase Blog (2025-06-09), here is our hands-on CodingCat.dev breakdown of what actually shipped, how it works under the hood, and how we wire it into production TypeScript, Next.js, Astro, SvelteKit, React, and Angular applications. The Firebase team announced a powerful new experimental feature in the Firebase AI Logic client SDK for Web: hybrid on-device inference! The Mental Model: Why Introducing hybrid on-device inference using Firebase AI Logic Matters for Web Devs As a Google Developer Expert (GDE) for Firebase and Senior Solutions Engineer at CodingCat.dev, I evaluate every Firebase update through one lens: does it reduce boilerplate while keeping our runtime security and cloud bills predictable? Firebase AI Logic lets you invoke Gemini and Imagen models from client and edge apps using the Firebase Web SDK while keeping your API credentials off the wire, enforcing Firebase App Check, and managing server-side prompt templates centrally. > AJ Pro-Tip from Alex: Combine Firebase AI Logic with server prompt templates, Firebase Remote Config feature flags, and project spend caps so you can iterate on prompts or swap Gemini models instantly without redeploying your Next.js or Astro frontend. How It Works: Technical Breakdown & Implementation Harness the power of on-device AI models with a seamless fallback to cloud-hosted models This ensures your generative AI-powered features are always available to your users, online or offline. Chrome on Desktop supports Gemini Nano, a powerful on-device model that can be downloaded and hosted in the browser on-demand. Running AI models directly on a user’s device offers some great advantages: - Enhanced privacy: Sensitive data can stay right on the user’s device. - Offline availability: Your app’s AI features can work even without an internet connection. - Cost savings: On-device inference is available at no cost, reducing your application’s operational expenses. - On-device performance: You can often get high performance thanks to hardware acceleration. You can build AI features that utilize all these advantages of an on-device model, but then fallback to a cloud-hosted model when its capabilities are needed. On-Device and In-Cloud Inference The Firebase AI Logic client SDK for Web has a hybrid on-device extension that uses the proposed W3C Prompt API for on-device model capabilities. It uses built-in, on-device models (like Gemini Nano in Chrome) when it’s available. If that’s not an option on a particular device or browser, the SDK falls back to using Gemini models on the server. This maximizes your reach, ensuring that your users can access your AI features, regardless of their device capabilities. How it works Using hybrid on-device inference is straightforward. The Firebase AI Logic client SDK for Web automatically handles the logic of checking for on-device model availability and falling back to a cloud-hosted model if necessary. Here’s a look at how you can implement hybrid inference in your web app: // Initialize the Gemini Developer API backend service const // Create a `GenerativeModel` instance // Set the mode, for example to use on-device model when possible const // run inference and log result const console. This implementation is almost identical to a regular getGenerativeModel call, but with the addition of the mode parameter that sets how you want to access a model. The SDK takes care of the rest! Furthermore, you can also configure this code to always use either an on-device or cloud-hosted model for inference by switching the `mode` parameter: // Run inference only on device. Does not fallback to cloud-hosted models. const // Run inference only on the cloud-hosted models. const If you’re curious, you can try out our demo **and view the codebase**. Production Web Architecture & Security Checklist Whether we are deploying on Firebase App Hosting, Cloudflare Workers, or Vercel, shipping AI features to production requires defense-in-depth across three layers: - Enforce Firebase App Check & Attestation: **Protect your web endpoints, Cloud Functions, Data Connect GraphQL resolvers, and Firebase AI Logic calls against unauthorized replay or billing abuse using Firebase App Check with reCAPTCHA Enterprise**. - **Validate Locally with the Firebase Emulator Suite: **Run deterministic integration tests against local Firestore, Auth, Functions, and Data Connect PostgreSQL emulators before promoting schema or rule changes to production. - **Keep Server & Client Boundaries Type-Safe: **Use the modular Firebase Web SDK and generated TypeScript bindings so SSR frameworks like Next.js, Astro, SvelteKit, and Angular serialize state cleanly without hydration flickers. Conclusion Accelerate your full-stack web and AI development by pairing Introducing hybrid on-device inference using Firebase AI Logic with strict Security Rules, Firebase App Check, and local emulator testing. Come tell us on X (@CodingCatDev) and LinkedIn (CodingCat.dev) how you are using Firebase in your stack, and subscribe to the CodingCat.dev YouTube Channel so you don't miss what we build next! Watch Our Related Firebase Video Tutorial on YouTube Want to see how we architect and debug Firebase web applications live? Watch our related CodingCat.dev session Firebase SQL with Data Connect below, and make sure to Subscribe to CodingCat.dev on YouTube for weekly full-stack web and AI deep dives: {% youtube N25khadfUag %} Resources, Official Docs & Related CodingCat.dev Guides - Primary Release Source: **Introducing hybrid on-device inference using Firebase AI Logic on the Official Firebase Blog (published 2025-06-09 by Siddharth Gupta, Erik Eldridge) - **Official Firebase Documentation: **Firebase Developer Docs & Web SDK Reference - **Related on CodingCat.dev: Transactions Updates for Data Connect - Related on CodingCat.dev: Fueld.AI's nutrition coach with Firebase and Google Cloud - Related on CodingCat.dev: Firebase SQL with Data Connect - Related on CodingCat.dev: Build a Custom No-Code App in 90 Minutes Frequently Asked Questions (FAQ) What are the key capabilities introduced in Introducing hybrid on-device inference using Firebase AI Logic? Introducing hybrid on-device inference using Firebase AI Logic (announced 2025-06-09 by the Firebase team) enhances the platform for Firebase, Google Cloud, Web Dev, AI, TypeScript workloads. The Firebase team announced a powerful new experimental feature in the Firebase AI Logic client SDK for Web: hybrid on-device inference! Developers can review the primary announcement on the Firebase Blog alongside our implementation patterns and production checklist above. How do web developers use Introducing hybrid on-device inference using Firebase AI Logic with Next.js, SvelteKit, React, or Angular? Web developers can integrate these features using the modular Firebase JavaScript/TypeScript SDK, Firebase Data Connect web bindings, and Firebase App Hosting. Always pair client-side SDK initialization with Firebase App Check and strict Security Rules, and test locally with the Firebase Local Emulator Suite before deploying. Where can I watch video tutorials on Firebase Web and AI development? You can watch our hands-on Firebase video walkthroughs on the CodingCat.dev YouTube Channel, including Firebase SQL with Data Connect, and follow us on X (@CodingCatDev) for weekly full-stack tutorials.
@cyrus_zei @ChromiumDev There's a skill you can use. Just ask your agent "Add WebMCP tools to this project" - github.com/GoogleChromeLabs/webmcp-tools/tree/mai…
@anquetil @_mahns We're experimenting with this skill: github.com/GoogleChromeLabs/webmcp-tools/tree/mai…. One thing you can try is installing it and then asking your agent "Audit the WebMCP tools in this repo"
google出品的图片压缩工具 GoogleChromeLabs/squoosh
Is this ported to GoogleChromeLabs/squoosh yet?
Web Workers in Firefox: Comlink, Transferable Objects, SharedArrayBuffer, and Module Workers Web Workers allow JavaScript to run in background threads separate from the main thread. This prevents long-running operations from blocking the UI, keeping the page responsive. Firefox has excellent Web Worker support with great DevTools integration. This guide covers Web Workers, Shared Workers, and the Comlink library. The Problem: The Single-Threaded Main Thread JavaScript is single-threaded by design. Any expensive computation — sorting a large array, processing CSV data, image manipulation, encryption — blocks the main thread and freezes the UI. Symptoms of main thread blocking: - UI doesn't respond to clicks during the operation - Animations stutter - DevTools Performance panel shows long yellow "scripting" blocks - Chrome/Firefox's "Page Unresponsive" dialog appears Web Workers solve this by moving the computation to a background thread. Creating a Basic Worker // main.js (runs in page) const worker = new Worker('/worker.js'); // Send message to worker worker.postMessage({ type: 'PROCESS', data: largeArray }); // Receive result from worker worker.onmessage = (event) => { const result = event.data; updateUI(result); }; worker.onerror = (error) => { console.error('Worker error:', error.message); }; // Terminate the worker when done worker.terminate(); // worker.js (background thread) self.onmessage = (event) => { const { type, data } = event.data; if (type === 'PROCESS') { // Heavy computation here - doesn't block UI const result = processData(data); self.postMessage(result); } }; function processData(data) { // Expensive operation return data.sort((a, b) => a.value - b.value); } Inline Workers with Blob URLs Create workers without a separate file: const workerCode = ` self.onmessage = (event) => { const result = expensiveComputation(event.data); self.postMessage(result); }; function expensiveComputation(data) { return data.map(x => x * 2); } `; const blob = new Blob([workerCode], { type: 'application/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); Transferable Objects (Zero-Copy) Normally, postMessage() copies data (serialized with structured clone). For large binary data, transfer ownership instead — zero-copy: const buffer = new ArrayBuffer(1024 * 1024 * 10); // 10MB // Transfer ownership (buffer becomes empty in main thread) worker.postMessage({ buffer }, [buffer]); // buffer.byteLength === 0 now in main thread // In worker, receive the transferred buffer self.onmessage = (event) => { const { buffer } = event.data; // Process buffer // Transfer back: self.postMessage({ buffer }, [buffer]); }; Transferable types: ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas. Comlink: Cleaner Worker API The raw postMessage API is verbose. Comlink wraps workers in a Proxy that makes async calls look like regular function calls: // worker-api.js import * as Comlink from 'comlink'; const api = { async processData(data) { return data.map(x => x * 2).filter(x => x > 10); }, async fetchAndProcess(url) { const response = await fetch(url); const data = await response.json(); return this.processData(data); } }; Comlink.expose(api); // main.js import * as Comlink from 'comlink'; const worker = new Worker('./worker-api.js', { type: 'module' }); const api = Comlink.wrap(worker); // Call worker functions as if they were local async functions const result = await api.processData([1, 5, 15, 20]); SharedArrayBuffer and Atomics For workers that need to share memory (requires COOP/COEP headers): // Create shared buffer const shared = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100); const array = new Int32Array(shared); // Send to worker (shared, not copied) worker.postMessage({ shared }); // Atomics for thread-safe operations Atomics.add(array, 0, 1); // thread-safe increment Atomics.store(array, 1, 42); // thread-safe write Atomics.load(array, 1); // thread-safe read Atomics.wait(array, 0, 0); // block worker until value changes (workers only) Firefox note: SharedArrayBuffer requires cross-origin isolation (COOP + COEP headers) in Firefox. If you see SharedArrayBuffer is not defined, add these response headers: Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp Module Workers Workers can use ES modules (Firefox 114+): // ESM worker const worker = new Worker('/worker.js', { type: 'module' }); // worker.js as ES module import { processData } from './utils.js'; import * as Comlink from 'comlink'; Comlink.expose({ processData }); Shared Workers A SharedWorker is shared across all tabs/windows of the same origin: // Each page connects to the same SharedWorker const worker = new SharedWorker('/shared-worker.js'); worker.port.start(); worker.port.postMessage('hello'); worker.port.onmessage = (event) => console.log(event.data); // shared-worker.js const clients = new Set(); self.onconnect = (event) => { const port = event.ports[0]; clients.add(port); port.onmessage = (event) => { // Broadcast to all connected tabs clients.forEach(client => client.postMessage(event.data)); }; port.start(); }; Use cases: real-time sync between tabs, shared WebSocket connection, cached data shared across tabs. Firefox DevTools for Workers Console: worker console.log() appears in the main page's DevTools console with a "worker" label. Easy to identify. Debugger: open DevTools → Debugger → the Sources panel shows worker files under "Workers". Set breakpoints directly in worker code. The debugger pauses inside the worker thread. about:debugging → Workers: shows all running workers and their status. Performance Measurement // Benchmark without and with worker const start = performance.now(); processData(largeArray); // main thread console.log('Main thread:', performance.now() - start, 'ms'); // With worker: const startW = performance.now(); worker.postMessage(largeArray); worker.onmessage = () => { console.log('Worker:', performance.now() - startW, 'ms'); // Note: includes serialization overhead }; Workers have message passing overhead — for very small operations, the overhead exceeds the benefit. Workers are valuable for operations >100ms.
Ahora puedes instalar tus herramientas de Webmcp como extensiones de Chrome y obtener información para agentes. GoogleChromeLabs/webmcp-extension t.co
🔴 WebMCP Agent Skills A collection of experimental AI agent skills to help developers integrate WebMCP into web apps. 👉WebMCP lets AI agents call JavaScript functions as tools, enabling frontend interaction without having to emulate human behavior. github.com/GoogleChromeLabs/webmcp-tools/tree/mai…