diff --git a/.changeset/sixty-teachers-tap.md b/.changeset/sixty-teachers-tap.md
new file mode 100644
index 000000000..122557751
--- /dev/null
+++ b/.changeset/sixty-teachers-tap.md
@@ -0,0 +1,26 @@
+---
+'@astrojs/vercel': minor
+---
+
+Enable Vercel Speed Insights and Vercel Web Analytics individually.
+Deprecates the `analytics` property in `astro.config.mjs` in favor of `speedInsights` and `webAnalytics`.
+
+If you're using the `analytics` property, you'll need to update your config to use the new properties:
+
+```diff
+// astro.config.mjs
+export default defineConfig({
+ adapter: vercel({
+- analytics: true,
++ webAnalytics: {
++ enabled: true
++ },
++ speedInsights: {
++ enabled: true
++ }
+ })
+});
+```
+
+Allow configuration of Web Analytics with all available configuration options.
+Bumps @vercel/analytics package to the latest version.
diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md
index db3a52a03..7776c2b30 100644
--- a/packages/integrations/vercel/README.md
+++ b/packages/integrations/vercel/README.md
@@ -85,13 +85,13 @@ vercel deploy --prebuilt
To configure this adapter, pass an object to the `vercel()` function call in `astro.config.mjs`:
-### analytics
+### Web Analytics
-**Type:** `boolean`
-**Available for:** Serverless, Static
-**Added in:** `@astrojs/vercel@3.1.0`
+**Type:** `VercelWebAnalyticsConfig`
+**Available for:** Serverless, Edge, Static
+**Added in:** `@astrojs/vercel@3.8.0`
-You can enable [Vercel Analytics](https://vercel.com/analytics) (including Web Vitals and Audiences) by setting `analytics: true`. This will inject Vercel’s tracking scripts into all your pages.
+You can enable [Vercel Web Analytics](https://vercel.com/docs/concepts/analytics) by setting `webAnalytics: { enabled: true }`. This will inject Vercel’s tracking scripts into all of your pages.
```js
// astro.config.mjs
@@ -101,7 +101,32 @@ import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'server',
adapter: vercel({
- analytics: true,
+ webAnalytics: {
+ enabled: true,
+ },
+ }),
+});
+```
+
+### Speed Insights
+
+You can enable [Vercel Speed Insights](https://vercel.com/docs/concepts/speed-insights) by setting `speedInsights: { enabled: true }`. This will collect and send Web Vital data to Vercel.
+
+**Type:** `VercelSpeedInsightsConfig`
+**Available for:** Serverless, Edge, Static
+**Added in:** `@astrojs/vercel@3.8.0`
+
+```js
+// astro.config.mjs
+import { defineConfig } from 'astro/config';
+import vercel from '@astrojs/vercel/serverless';
+
+export default defineConfig({
+ output: 'server',
+ adapter: vercel({
+ speedInsights: {
+ enabled: true,
+ },
}),
});
```
diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json
index 34bbb269b..60c0878f1 100644
--- a/packages/integrations/vercel/package.json
+++ b/packages/integrations/vercel/package.json
@@ -22,7 +22,7 @@
"./serverless": "./dist/serverless/adapter.js",
"./serverless/entrypoint": "./dist/serverless/entrypoint.js",
"./static": "./dist/static/adapter.js",
- "./analytics": "./dist/analytics.js",
+ "./speed-insights": "./dist/speed-insights.js",
"./build-image-service": "./dist/image/build-service.js",
"./dev-image-service": "./dist/image/dev-service.js",
"./squoosh-dev-service": "./dist/image/squoosh-dev-service.js",
diff --git a/packages/integrations/vercel/src/lib/env.ts b/packages/integrations/vercel/src/lib/speed-insights.ts
similarity index 68%
rename from packages/integrations/vercel/src/lib/env.ts
rename to packages/integrations/vercel/src/lib/speed-insights.ts
index 01d8c76a5..8e3639536 100644
--- a/packages/integrations/vercel/src/lib/env.ts
+++ b/packages/integrations/vercel/src/lib/speed-insights.ts
@@ -1,3 +1,17 @@
+export type VercelSpeedInsightsConfig = {
+ enabled: boolean;
+};
+
+export function getSpeedInsightsViteConfig(enabled?: boolean) {
+ if (enabled) {
+ return {
+ define: exposeEnv(['VERCEL_ANALYTICS_ID']),
+ };
+ }
+
+ return {};
+}
+
/**
* While Vercel adds the `PUBLIC_` prefix for their `VERCEL_` env vars by default, some env vars
* like `VERCEL_ANALYTICS_ID` aren't, so handle them here so that it works correctly in runtime.
diff --git a/packages/integrations/vercel/src/lib/web-analytics.ts b/packages/integrations/vercel/src/lib/web-analytics.ts
new file mode 100644
index 000000000..d6ee4d78d
--- /dev/null
+++ b/packages/integrations/vercel/src/lib/web-analytics.ts
@@ -0,0 +1,30 @@
+export type VercelWebAnalyticsConfig = {
+ enabled: boolean;
+};
+
+export async function getInjectableWebAnalyticsContent({
+ mode,
+}: {
+ mode: 'development' | 'production';
+}) {
+ const base = `window.va = window.va || function () { (window.vaq = window.vaq || []).push(arguments); };`;
+
+ if (mode === 'development') {
+ return `
+ ${base}
+ var script = document.createElement('script');
+ script.defer = true;
+ script.src = 'https://cdn.vercel-insights.com/v1/script.debug.js';
+ var head = document.querySelector('head');
+ head.appendChild(script);
+ `;
+ }
+
+ return `${base}
+ var script = document.createElement('script');
+ script.defer = true;
+ script.src = '/_vercel/insights/script.js';
+ var head = document.querySelector('head');
+ head.appendChild(script);
+ `;
+}
diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts
index 22785abf5..12fb2caa7 100644
--- a/packages/integrations/vercel/src/serverless/adapter.ts
+++ b/packages/integrations/vercel/src/serverless/adapter.ts
@@ -15,10 +15,17 @@ import {
type DevImageService,
type VercelImageConfig,
} from '../image/shared.js';
-import { exposeEnv } from '../lib/env.js';
import { getVercelOutput, removeDir, writeJson } from '../lib/fs.js';
import { copyDependenciesToFunction } from '../lib/nft.js';
import { getRedirects } from '../lib/redirects.js';
+import {
+ getSpeedInsightsViteConfig,
+ type VercelSpeedInsightsConfig,
+} from '../lib/speed-insights.js';
+import {
+ getInjectableWebAnalyticsContent,
+ type VercelWebAnalyticsConfig,
+} from '../lib/web-analytics.js';
import { generateEdgeMiddleware } from './middleware.js';
const PACKAGE_NAME = '@astrojs/vercel/serverless';
@@ -64,9 +71,14 @@ function getAdapter({
}
export interface VercelServerlessConfig {
+ /**
+ * @deprecated
+ */
+ analytics?: boolean;
+ webAnalytics?: VercelWebAnalyticsConfig;
+ speedInsights?: VercelSpeedInsightsConfig;
includeFiles?: string[];
excludeFiles?: string[];
- analytics?: boolean;
imageService?: boolean;
imagesConfig?: VercelImageConfig;
devImageService?: DevImageService;
@@ -75,9 +87,11 @@ export interface VercelServerlessConfig {
}
export default function vercelServerless({
+ analytics,
+ webAnalytics,
+ speedInsights,
includeFiles,
excludeFiles,
- analytics,
imageService,
imagesConfig,
devImageService = 'sharp',
@@ -131,12 +145,25 @@ export default function vercelServerless({
return {
name: PACKAGE_NAME,
hooks: {
- 'astro:config:setup': ({ command, config, updateConfig, injectScript }) => {
- if (command === 'build' && analytics) {
- injectScript('page', 'import "@astrojs/vercel/analytics"');
+ 'astro:config:setup': async ({ command, config, updateConfig, injectScript, logger }) => {
+ if (webAnalytics?.enabled || analytics) {
+ if (analytics) {
+ logger.warn(
+ `The \`analytics\` property is deprecated. Please use the new \`webAnalytics\` and \`speedInsights\` properties instead.`
+ );
+ }
+
+ injectScript(
+ 'head-inline',
+ await getInjectableWebAnalyticsContent({
+ mode: command === 'dev' ? 'development' : 'production',
+ })
+ );
+ }
+ if (command === 'build' && (speedInsights?.enabled || analytics)) {
+ injectScript('page', 'import "@astrojs/vercel/speed-insights"');
}
const outDir = getVercelOutput(config.root);
- const viteDefine = exposeEnv(['VERCEL_ANALYTICS_ID']);
updateConfig({
outDir,
build: {
@@ -145,7 +172,7 @@ export default function vercelServerless({
server: new URL('./dist/', config.root),
},
vite: {
- define: viteDefine,
+ ...getSpeedInsightsViteConfig(speedInsights?.enabled || analytics),
ssr: {
external: ['@vercel/nft'],
},
diff --git a/packages/integrations/vercel/src/analytics.ts b/packages/integrations/vercel/src/speed-insights.ts
similarity index 60%
rename from packages/integrations/vercel/src/analytics.ts
rename to packages/integrations/vercel/src/speed-insights.ts
index 95dee83e3..cd2ae7fe8 100644
--- a/packages/integrations/vercel/src/analytics.ts
+++ b/packages/integrations/vercel/src/speed-insights.ts
@@ -1,8 +1,7 @@
-import { inject } from '@vercel/analytics';
import type { Metric } from 'web-vitals';
-import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals';
+import { onCLS, onFCP, onFID, onLCP, onTTFB } from 'web-vitals';
-const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
+const SPEED_INSIGHTS_INTAKE = 'https://vitals.vercel-analytics.com/v1/vitals';
type Options = { path: string; analyticsId: string };
@@ -14,7 +13,7 @@ const getConnectionSpeed = () => {
: '';
};
-const sendToAnalytics = (metric: Metric, options: Options) => {
+const sendToSpeedInsights = (metric: Metric, options: Options) => {
const body = {
dsn: options.analyticsId,
id: metric.id,
@@ -28,9 +27,9 @@ const sendToAnalytics = (metric: Metric, options: Options) => {
type: 'application/x-www-form-urlencoded',
});
if (navigator.sendBeacon) {
- navigator.sendBeacon(vitalsUrl, blob);
+ navigator.sendBeacon(SPEED_INSIGHTS_INTAKE, blob);
} else
- fetch(vitalsUrl, {
+ fetch(SPEED_INSIGHTS_INTAKE, {
body: blob,
method: 'POST',
credentials: 'omit',
@@ -38,27 +37,29 @@ const sendToAnalytics = (metric: Metric, options: Options) => {
});
};
-function webVitals() {
+function collectWebVitals() {
const analyticsId = (import.meta as any).env.PUBLIC_VERCEL_ANALYTICS_ID;
+
if (!analyticsId) {
- console.error('[Analytics] VERCEL_ANALYTICS_ID not found');
+ console.error('[Speed Insights] VERCEL_ANALYTICS_ID not found');
return;
}
+
const options: Options = { path: window.location.pathname, analyticsId };
+
try {
- getFID((metric) => sendToAnalytics(metric, options));
- getTTFB((metric) => sendToAnalytics(metric, options));
- getLCP((metric) => sendToAnalytics(metric, options));
- getCLS((metric) => sendToAnalytics(metric, options));
- getFCP((metric) => sendToAnalytics(metric, options));
+ onFID((metric) => sendToSpeedInsights(metric, options));
+ onTTFB((metric) => sendToSpeedInsights(metric, options));
+ onLCP((metric) => sendToSpeedInsights(metric, options));
+ onCLS((metric) => sendToSpeedInsights(metric, options));
+ onFCP((metric) => sendToSpeedInsights(metric, options));
} catch (err) {
- console.error('[Analytics]', err);
+ console.error('[Speed Insights]', err);
}
}
const mode = (import.meta as any).env.MODE as 'development' | 'production';
-inject({ mode });
if (mode === 'production') {
- webVitals();
+ collectWebVitals();
}
diff --git a/packages/integrations/vercel/src/static/adapter.ts b/packages/integrations/vercel/src/static/adapter.ts
index 27bc2fe2d..df2995c37 100644
--- a/packages/integrations/vercel/src/static/adapter.ts
+++ b/packages/integrations/vercel/src/static/adapter.ts
@@ -6,10 +6,17 @@ import {
type DevImageService,
type VercelImageConfig,
} from '../image/shared.js';
-import { exposeEnv } from '../lib/env.js';
import { emptyDir, getVercelOutput, writeJson } from '../lib/fs.js';
import { isServerLikeOutput } from '../lib/prerender.js';
import { getRedirects } from '../lib/redirects.js';
+import {
+ getSpeedInsightsViteConfig,
+ type VercelSpeedInsightsConfig,
+} from '../lib/speed-insights.js';
+import {
+ getInjectableWebAnalyticsContent,
+ type VercelWebAnalyticsConfig,
+} from '../lib/web-analytics.js';
const PACKAGE_NAME = '@astrojs/vercel/static';
@@ -34,7 +41,12 @@ function getAdapter(): AstroAdapter {
}
export interface VercelStaticConfig {
+ /**
+ * @deprecated
+ */
analytics?: boolean;
+ webAnalytics?: VercelWebAnalyticsConfig;
+ speedInsights?: VercelSpeedInsightsConfig;
imageService?: boolean;
imagesConfig?: VercelImageConfig;
devImageService?: DevImageService;
@@ -42,6 +54,8 @@ export interface VercelStaticConfig {
export default function vercelStatic({
analytics,
+ webAnalytics,
+ speedInsights,
imageService,
imagesConfig,
devImageService = 'sharp',
@@ -51,12 +65,25 @@ export default function vercelStatic({
return {
name: '@astrojs/vercel',
hooks: {
- 'astro:config:setup': ({ command, config, injectScript, updateConfig }) => {
- if (command === 'build' && analytics) {
- injectScript('page', 'import "@astrojs/vercel/analytics"');
+ 'astro:config:setup': async ({ command, config, injectScript, updateConfig, logger }) => {
+ if (webAnalytics?.enabled || analytics) {
+ if (analytics) {
+ logger.warn(
+ `The \`analytics\` property is deprecated. Please use the new \`webAnalytics\` and \`speedInsights\` properties instead.`
+ );
+ }
+
+ injectScript(
+ 'head-inline',
+ await getInjectableWebAnalyticsContent({
+ mode: command === 'dev' ? 'development' : 'production',
+ })
+ );
+ }
+ if (command === 'build' && (speedInsights?.enabled || analytics)) {
+ injectScript('page', 'import "@astrojs/vercel/speed-insights"');
}
const outDir = new URL('./static/', getVercelOutput(config.root));
- const viteDefine = exposeEnv(['VERCEL_ANALYTICS_ID']);
updateConfig({
outDir,
build: {
@@ -64,7 +91,7 @@ export default function vercelStatic({
redirects: false,
},
vite: {
- define: viteDefine,
+ ...getSpeedInsightsViteConfig(speedInsights?.enabled || analytics),
},
...getAstroImageConfig(
imageService,
diff --git a/packages/integrations/vercel/src/types.d.ts b/packages/integrations/vercel/src/types.d.ts
new file mode 100644
index 000000000..1c5b8d2db
--- /dev/null
+++ b/packages/integrations/vercel/src/types.d.ts
@@ -0,0 +1,3 @@
+import type { AnalyticsProps } from '@vercel/analytics';
+
+export type VercelWebAnalyticsBeforeSend = AnalyticsProps['beforeSend'];
diff --git a/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/astro.config.mjs b/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/astro.config.mjs
new file mode 100644
index 000000000..b505034ce
--- /dev/null
+++ b/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/astro.config.mjs
@@ -0,0 +1,10 @@
+import { defineConfig } from 'astro/config';
+import vercel from '@astrojs/vercel/serverless';
+
+export default defineConfig({
+ adapter: vercel({
+ speedInsights: {
+ enabled: true
+ }
+ })
+});
diff --git a/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/package.json b/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/package.json
new file mode 100644
index 000000000..4c6867fd3
--- /dev/null
+++ b/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/astro-vercel-with-speed-insights-enabled-output-as-server",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/vercel": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/src/pages/one.astro b/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/src/pages/one.astro
new file mode 100644
index 000000000..0c7fb90a7
--- /dev/null
+++ b/packages/integrations/vercel/test/fixtures/with-speed-insights-enabled/output-as-server/src/pages/one.astro
@@ -0,0 +1,8 @@
+
+