An OpenMage extension that integrates Chrome User Experience Report (CrUX) data into the admin backend.
- Display Core Web Vitals data (LCP, INP, CLS, FCP) from Chrome User Experience Report
- Support for different form factors (desktop, mobile, tablet)
- Show form factor distribution for the site
- Rate metrics according to Google standards (good, average, poor)
- Simple configuration through the admin panel
- Download the repository
- Copy the files to your OpenMage root directory
- Clear the cache
composer require mm/openmage-cruxdata
modman clone https://github.com/empiricompany/openmage-cruxdata.git
- Log in to your OpenMage admin panel
- Go to System > Configuration > CrUX Data > General Settings
- Enter your Google API key with CrUX API access
- (Optional) Enter a custom API endpoint domain if you're using a proxy (e.g., https://your-worker.workers.dev)
- Save the configuration
To obtain a Google API key:
- Go to the Google Cloud Console
- Create a new project or select an existing project
- Enable the Chrome UX Report API
- Create an API key in the credentials section
After configuring the extension, you can access the CrUX data by going to:
Report > CrUX Data > Report
You can use a custom API endpoint (like a CloudFlare Workers proxy) to:
- Hide your API key from the client
- Add caching at the proxy level
- Implement custom rate limiting
- Add additional security measures
To set up a CloudFlare Workers proxy:
- Create a new CloudFlare Worker
- Implement a proxy that forwards requests to the CrUX API with your API key
- Enter the Worker domain (e.g., https://your-worker.workers.dev) in the Custom API Endpoint field in the extension configuration
Example CloudFlare Worker code with caching:
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Build URL with API key (stored in Worker environment)
const APIkey = await env.curxdata_api_key.get()
const upstreamUrl = `https://chromeuxreport.googleapis.com${url.pathname}?key=${APIkey}`;
const method = request.method.toUpperCase();
// Only POST and OPTIONS are supported
if (method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
if (method !== 'POST') {
return new Response('Only POST supported', { status: 405 });
}
// Get body as text for content-based caching
const body = await request.text();
// Daily cache key
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
const cacheKey = new Request(`${upstreamUrl}|${body}|${today}`, { method: 'GET' });
const cache = caches.default;
let response = await cache.match(cacheKey);
if (response) {
return new Response(response.body, {
...response,
headers: {
...Object.fromEntries(response.headers),
'Access-Control-Allow-Origin': '*',
'X-Worker-Cache': 'HIT',
},
});
}
// Call the actual API
response = await fetch(upstreamUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
});
// Cache only if status is 200
if (response.status === 200) {
ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
return new Response(response.body, {
...response,
headers: {
...Object.fromEntries(response.headers),
'Access-Control-Allow-Origin': '*',
'X-Worker-Cache': 'MISS',
},
});
},
};
MIT