Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[@astrojs/vercel] Individually enable Speed Insights and Web Analytics #8021

Merged
merged 29 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4a7bad4
Individually enable Speed Insights and Web Analytics
chriswdmr Aug 10, 2023
3db8381
Update pnpm-lock.yaml
chriswdmr Aug 10, 2023
6b4fc91
Remove .only on tests
chriswdmr Aug 10, 2023
905a2a9
Merge remote-tracking branch 'upstream/main'
chriswdmr Aug 28, 2023
e12538a
Fix build
chriswdmr Aug 29, 2023
2e11ca5
Merge branch 'withastro:main' into main
chriswdmr Aug 29, 2023
97511dd
Move `beforeSend` out of config
chriswdmr Aug 29, 2023
30003d7
Address feedback from review
chriswdmr Aug 29, 2023
6d91119
Update README.md
chriswdmr Aug 29, 2023
99b07d4
Add back the `analytics` property and add deprecation warning when used
chriswdmr Aug 29, 2023
93beaf9
Add migration guide for the deprecated `analytics` property
chriswdmr Aug 29, 2023
9598759
Merge branch 'main' into main
chriswdmr Aug 30, 2023
5494466
Merge branch 'main' into main
chriswdmr Sep 4, 2023
05df84f
Update packages/integrations/vercel/README.md
chriswdmr Sep 4, 2023
704640a
Update README.md
chriswdmr Sep 4, 2023
a8d0e77
Merge branch 'main' into main
chriswdmr Sep 4, 2023
2608fee
Merge branch 'main' into main
chriswdmr Sep 4, 2023
d0cf8c7
Merge branch 'main' into main
chriswdmr Sep 5, 2023
45c522f
Merge branch 'main' into main
chriswdmr Sep 5, 2023
ad86e9f
Merge branch 'main' into main
matthewp Sep 5, 2023
e492e94
Fix external dependency issue
chriswdmr Sep 6, 2023
09de89b
Merge branch 'main' into main
chriswdmr Sep 6, 2023
a44d8bb
Simplify plugin and reduce scope
chriswdmr Sep 7, 2023
88cf8e1
Update .changeset/sixty-teachers-tap.md
chriswdmr Sep 12, 2023
4441016
Apply feedback from review
chriswdmr Sep 12, 2023
f34dcc9
Merge remote-tracking branch 'upstream/main'
chriswdmr Sep 12, 2023
bf986d5
Move exposeEnv to speed-insights since it's only used there
chriswdmr Sep 13, 2023
0a63721
Merge branch 'main' into main
chriswdmr Sep 13, 2023
1dae36e
Merge branch 'main' into main
chriswdmr Sep 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .changeset/sixty-teachers-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@astrojs/vercel': major
chriswdmr marked this conversation as resolved.
Show resolved Hide resolved
---

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
+ }
chriswdmr marked this conversation as resolved.
Show resolved Hide resolved
})
});
```

Allow configuration of Web Analytics with all available configuration options.
Bumps @vercel/analytics package to the latest version.
56 changes: 50 additions & 6 deletions packages/integrations/vercel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ vercel deploy --prebuilt

To configure this adapter, pass an object to the `vercel()` function call in `astro.config.mjs`:

### analytics
### Web Analytics

**Type:** `boolean`<br>
**Available for:** Serverless, Static<br>
**Added in:** `@astrojs/vercel@3.1.0`
**Type:** `VercelWebAnalyticsConfig`<br>
**Available for:** Serverless, Edge, Static<br>
**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.
Alternatively, you can also pass [configuration options](https://vercel.com/docs/concepts/analytics/package) (except functions like `beforeSend`) via the `config` property inside `webAnalytics`.
chriswdmr marked this conversation as resolved.
Show resolved Hide resolved

```js
// astro.config.mjs
Expand All @@ -101,7 +102,50 @@ import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'server',
adapter: vercel({
analytics: true,
webAnalytics: {
enabled: true,
},
}),
});
```

#### `beforeSend`

To define the `beforeSend` function, you need to create a separate file inside your root called `vercel-web-analytics.ts`.
If you're not using TypeScript, you can define the function inside `vercel-web-analytics.js`.
chriswdmr marked this conversation as resolved.
Show resolved Hide resolved

```ts
// vercel-web-analytics.ts
import type { VercelWebAnalyticsBeforeSend } from '@astrojs/vercel';

export const beforeSend: VercelWebAnalyticsBeforeSend = (event) => {
// Ignore all events that have a `/private` inside the URL
if (event.url.includes('/private')) {
return null;
}
return event;
};
```

### 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`<br>
**Available for:** Serverless, Edge, Static<br>
**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,
},
}),
});
```
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
"./package.json": "./package.json"
Expand Down
15 changes: 15 additions & 0 deletions packages/integrations/vercel/src/lib/speed-insights.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { exposeEnv } from './env';

export type VercelSpeedInsightsConfig = {
enabled: boolean;
};

export function getSpeedInsightsViteConfig(enabled?: boolean) {
if (enabled) {
return {
define: exposeEnv(['VERCEL_ANALYTICS_ID']),
};
}

return {};
}
76 changes: 76 additions & 0 deletions packages/integrations/vercel/src/lib/web-analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { AstroError } from 'astro/errors';
import type { AnalyticsProps } from '@vercel/analytics';
import { fileURLToPath } from 'url';
import { existsSync } from 'node:fs';
import type { AstroIntegrationLogger } from 'astro';

export type VercelWebAnalyticsConfig = {
enabled: boolean;
config?: Omit<AnalyticsProps, 'beforeSend'>;
};

async function getWebAnalyticsFunctions({
root,
logger,
}: {
root: URL;
logger: AstroIntegrationLogger;
}) {
const tsPath = fileURLToPath(new URL('./vercel-web-analytics.ts', root));
const jsPath = fileURLToPath(new URL('./vercel-web-analytics.js', root));

const tsFileExists = existsSync(tsPath);
const jsFileExists = existsSync(jsPath);

if (tsFileExists && jsFileExists) {
logger.warn(
`@astrojs/vercel: Both \`vercel-web-analytics.ts\` and \`vercel-web-analytics.js\` exist. Using \`vercel-web-analytics.ts\`.`
);
}

if (!tsFileExists && !jsFileExists) {
logger.debug(
`@astrojs/vercel: \`vercel-web-analytics.ts\` or \`vercel-web-analytics.js\` not found.`
);

return {
chriswdmr marked this conversation as resolved.
Show resolved Hide resolved
beforeSend: undefined,
};
}

const functions = await import(
tsFileExists ? /* @vite-ignore */ tsPath : /* @vite-ignore */ jsPath
);

if (typeof functions.beforeSend !== 'function') {
throw new AstroError(
`@astrojs/vercel: \`vercel-web-analytics.${
tsFileExists ? 'ts' : 'js'
}\` must export a \`beforeSend\` function.`
);
}

return {
beforeSend: functions.beforeSend,
};
}

export async function getInjectableWebAnalyticsContent({
config,
astro,
}: {
config: Omit<AnalyticsProps, 'beforeSend'> | undefined;
astro: {
root: URL;
logger: AstroIntegrationLogger;
};
}) {
const { beforeSend } = await getWebAnalyticsFunctions(astro);

return `import { inject } from '@vercel/analytics';
inject({
mode: ${config?.mode},
beforeSend: ${beforeSend},
debug: ${config?.debug}
});`;
}
50 changes: 42 additions & 8 deletions packages/integrations/vercel/src/serverless/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import glob from 'fast-glob';
import { basename } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { defaultImageConfig, getImageConfig, 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 { generateEdgeMiddleware } from './middleware.js';
import {
getInjectableWebAnalyticsContent,
type VercelWebAnalyticsConfig,
} from '../lib/web-analytics.js';
import {
getSpeedInsightsViteConfig,
type VercelSpeedInsightsConfig,
} from '../lib/speed-insights.js';

const PACKAGE_NAME = '@astrojs/vercel/serverless';
export const ASTRO_LOCALS_HEADER = 'x-astro-locals';
Expand Down Expand Up @@ -53,19 +60,26 @@ function getAdapter({
}

export interface VercelServerlessConfig {
/**
* @deprecated
*/
analytics?: boolean;
webAnalytics?: VercelWebAnalyticsConfig;
speedInsights?: VercelSpeedInsightsConfig;
includeFiles?: string[];
excludeFiles?: string[];
analytics?: boolean;
imageService?: boolean;
imagesConfig?: VercelImageConfig;
edgeMiddleware?: boolean;
functionPerRoute?: boolean;
}

export default function vercelServerless({
analytics,
webAnalytics,
speedInsights,
includeFiles,
excludeFiles,
analytics,
imageService,
imagesConfig,
functionPerRoute = true,
Expand Down Expand Up @@ -107,12 +121,32 @@ 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(
`@astrojs/vercel: the \`analytics\` property is deprecated. Please use the new \`webAnalytics\` and \`speedInsights\` properties instead.`
chriswdmr marked this conversation as resolved.
Show resolved Hide resolved
);
}

injectScript(
'page',
ematipico marked this conversation as resolved.
Show resolved Hide resolved
await getInjectableWebAnalyticsContent({
config: {
...(webAnalytics?.config || {}),
mode: command === 'dev' ? 'development' : 'production',
},
astro: {
root: config.root,
logger,
},
})
);
}
if (command === 'build' && (speedInsights?.enabled || analytics)) {
injectScript('page', 'import "@astrojs/vercel/speed-insights"');
chriswdmr marked this conversation as resolved.
Show resolved Hide resolved
}
const outDir = getVercelOutput(config.root);
const viteDefine = exposeEnv(['VERCEL_ANALYTICS_ID']);
updateConfig({
outDir,
build: {
Expand All @@ -121,7 +155,7 @@ export default function vercelServerless({
server: new URL('./dist/', config.root),
},
vite: {
define: viteDefine,
...getSpeedInsightsViteConfig(speedInsights?.enabled),
ssr: {
external: ['@vercel/nft'],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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 };

Expand All @@ -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,
Expand All @@ -28,37 +27,39 @@ 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',
keepalive: true,
});
};

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();
}
Loading
Loading