mirror of
https://github.com/withastro/astro.git
synced 2025-01-20 22:12:38 -05:00
feat(vercel): Add support for analytics (Audiences & Web Vitals) (#6148)
* feat(intergration/vercel): add vercel analytics support * docs(intergration/vercel): add vercel analytics prop * docs(intergration/vercel): bump version to 3.1.0 * Update packages/integrations/vercel/README.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * docs(intergration/vercel): add file name for example * feat(intergration/vercel): convert analytics to ts and support in edge * docs(intergration/vercel): move file names to code blocks as comments * fix(intergration/vercel): remove unused import * feat(intergration/vercel): add analytics support to static mode * chore(intergration/vercel): revert version change * style(intergration/vercel): add a blank line after astro import * chore(intergration/vercel): generate file by changeset * Update .changeset/eighty-bobcats-deliver.md Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/integrations/vercel/README.md Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/integrations/vercel/src/analytics.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * chore(intergration/vercel): simplify analytics script --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
This commit is contained in:
parent
ec2f2a31de
commit
23c60cfa45
8 changed files with 137 additions and 7 deletions
5
.changeset/eighty-bobcats-deliver.md
Normal file
5
.changeset/eighty-bobcats-deliver.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/vercel': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add vercel analytics support
|
|
@ -87,6 +87,26 @@ vercel deploy --prebuilt
|
||||||
|
|
||||||
To configure this adapter, pass an object to the `vercel()` function call in `astro.config.mjs`:
|
To configure this adapter, pass an object to the `vercel()` function call in `astro.config.mjs`:
|
||||||
|
|
||||||
|
### analytics
|
||||||
|
|
||||||
|
> **Type:** `boolean`
|
||||||
|
> **Available for:** Serverless, Edge, Static
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import vercel from '@astrojs/vercel/serverless';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
output: 'server',
|
||||||
|
adapter: vercel({
|
||||||
|
analytics: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### includeFiles
|
### includeFiles
|
||||||
|
|
||||||
> **Type:** `string[]`
|
> **Type:** `string[]`
|
||||||
|
@ -95,6 +115,7 @@ To configure this adapter, pass an object to the `vercel()` function call in `as
|
||||||
Use this property to force files to be bundled with your function. This is helpful when you notice missing files.
|
Use this property to force files to be bundled with your function. This is helpful when you notice missing files.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// astro.config.mjs
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import vercel from '@astrojs/vercel/serverless';
|
import vercel from '@astrojs/vercel/serverless';
|
||||||
|
|
||||||
|
@ -109,7 +130,6 @@ export default defineConfig({
|
||||||
> **Note**
|
> **Note**
|
||||||
> When building for the Edge, all the dependencies get bundled in a single file to save space. **No extra file will be bundled**. So, if you _need_ some file inside the function, you have to specify it in `includeFiles`.
|
> When building for the Edge, all the dependencies get bundled in a single file to save space. **No extra file will be bundled**. So, if you _need_ some file inside the function, you have to specify it in `includeFiles`.
|
||||||
|
|
||||||
|
|
||||||
### excludeFiles
|
### excludeFiles
|
||||||
|
|
||||||
> **Type:** `string[]`
|
> **Type:** `string[]`
|
||||||
|
@ -118,6 +138,7 @@ export default defineConfig({
|
||||||
Use this property to exclude any files from the bundling process that would otherwise be included.
|
Use this property to exclude any files from the bundling process that would otherwise be included.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// astro.config.mjs
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import vercel from '@astrojs/vercel/serverless';
|
import vercel from '@astrojs/vercel/serverless';
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"./serverless": "./dist/serverless/adapter.js",
|
"./serverless": "./dist/serverless/adapter.js",
|
||||||
"./serverless/entrypoint": "./dist/serverless/entrypoint.js",
|
"./serverless/entrypoint": "./dist/serverless/entrypoint.js",
|
||||||
"./static": "./dist/static/adapter.js",
|
"./static": "./dist/static/adapter.js",
|
||||||
|
"./analytics": "./dist/analytics.js",
|
||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"typesVersions": {
|
"typesVersions": {
|
||||||
|
@ -45,9 +46,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/webapi": "^2.0.0",
|
"@astrojs/webapi": "^2.0.0",
|
||||||
|
"@vercel/analytics": "^0.1.8",
|
||||||
"@vercel/nft": "^0.22.1",
|
"@vercel/nft": "^0.22.1",
|
||||||
"fast-glob": "^3.2.11",
|
"fast-glob": "^3.2.11",
|
||||||
"set-cookie-parser": "^2.5.1"
|
"set-cookie-parser": "^2.5.1",
|
||||||
|
"web-vitals": "^3.1.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"astro": "workspace:^2.0.8"
|
"astro": "workspace:^2.0.8"
|
||||||
|
|
64
packages/integrations/vercel/src/analytics.ts
Normal file
64
packages/integrations/vercel/src/analytics.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { inject } from '@vercel/analytics';
|
||||||
|
import type { Metric } from 'web-vitals';
|
||||||
|
import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals';
|
||||||
|
|
||||||
|
const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
|
||||||
|
|
||||||
|
type Options = { path: string; analyticsId: string };
|
||||||
|
|
||||||
|
const getConnectionSpeed = () => {
|
||||||
|
return 'connection' in navigator &&
|
||||||
|
navigator['connection'] &&
|
||||||
|
'effectiveType' in (navigator['connection'] as unknown as { effectiveType: string })
|
||||||
|
? (navigator['connection'] as unknown as { effectiveType: string })['effectiveType']
|
||||||
|
: '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendToAnalytics = (metric: Metric, options: Options) => {
|
||||||
|
const body = {
|
||||||
|
dsn: options.analyticsId,
|
||||||
|
id: metric.id,
|
||||||
|
page: options.path,
|
||||||
|
href: location.href,
|
||||||
|
event_name: metric.name,
|
||||||
|
value: metric.value.toString(),
|
||||||
|
speed: getConnectionSpeed(),
|
||||||
|
};
|
||||||
|
const blob = new Blob([new URLSearchParams(body).toString()], {
|
||||||
|
type: 'application/x-www-form-urlencoded',
|
||||||
|
});
|
||||||
|
if (navigator.sendBeacon) {
|
||||||
|
navigator.sendBeacon(vitalsUrl, blob);
|
||||||
|
} else
|
||||||
|
fetch(vitalsUrl, {
|
||||||
|
body: blob,
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'omit',
|
||||||
|
keepalive: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function webVitals() {
|
||||||
|
const analyticsId = (import.meta as any).env.PUBLIC_VERCEL_ANALYTICS_ID;
|
||||||
|
if (!analyticsId) {
|
||||||
|
console.error('[Analytics] 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));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Analytics]', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = (import.meta as any).env.MODE as 'development' | 'production';
|
||||||
|
|
||||||
|
inject({ mode });
|
||||||
|
if (mode === 'production') {
|
||||||
|
webVitals();
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
|
import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
|
||||||
|
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import { relative as relativePath } from 'node:path';
|
import { relative as relativePath } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
@ -24,9 +25,13 @@ function getAdapter(): AstroAdapter {
|
||||||
|
|
||||||
export interface VercelEdgeConfig {
|
export interface VercelEdgeConfig {
|
||||||
includeFiles?: string[];
|
includeFiles?: string[];
|
||||||
|
analytics?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function vercelEdge({ includeFiles = [] }: VercelEdgeConfig = {}): AstroIntegration {
|
export default function vercelEdge({
|
||||||
|
includeFiles = [],
|
||||||
|
analytics,
|
||||||
|
}: VercelEdgeConfig = {}): AstroIntegration {
|
||||||
let _config: AstroConfig;
|
let _config: AstroConfig;
|
||||||
let buildTempFolder: URL;
|
let buildTempFolder: URL;
|
||||||
let functionFolder: URL;
|
let functionFolder: URL;
|
||||||
|
@ -35,7 +40,10 @@ export default function vercelEdge({ includeFiles = [] }: VercelEdgeConfig = {})
|
||||||
return {
|
return {
|
||||||
name: PACKAGE_NAME,
|
name: PACKAGE_NAME,
|
||||||
hooks: {
|
hooks: {
|
||||||
'astro:config:setup': ({ config, updateConfig }) => {
|
'astro:config:setup': ({ config, updateConfig, injectScript }) => {
|
||||||
|
if (analytics) {
|
||||||
|
injectScript('page', 'import "@astrojs/vercel/analytics"');
|
||||||
|
}
|
||||||
const outDir = getVercelOutput(config.root);
|
const outDir = getVercelOutput(config.root);
|
||||||
updateConfig({
|
updateConfig({
|
||||||
outDir,
|
outDir,
|
||||||
|
|
|
@ -19,11 +19,13 @@ function getAdapter(): AstroAdapter {
|
||||||
export interface VercelServerlessConfig {
|
export interface VercelServerlessConfig {
|
||||||
includeFiles?: string[];
|
includeFiles?: string[];
|
||||||
excludeFiles?: string[];
|
excludeFiles?: string[];
|
||||||
|
analytics?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function vercelServerless({
|
export default function vercelServerless({
|
||||||
includeFiles,
|
includeFiles,
|
||||||
excludeFiles,
|
excludeFiles,
|
||||||
|
analytics,
|
||||||
}: VercelServerlessConfig = {}): AstroIntegration {
|
}: VercelServerlessConfig = {}): AstroIntegration {
|
||||||
let _config: AstroConfig;
|
let _config: AstroConfig;
|
||||||
let buildTempFolder: URL;
|
let buildTempFolder: URL;
|
||||||
|
@ -33,7 +35,10 @@ export default function vercelServerless({
|
||||||
return {
|
return {
|
||||||
name: PACKAGE_NAME,
|
name: PACKAGE_NAME,
|
||||||
hooks: {
|
hooks: {
|
||||||
'astro:config:setup': ({ config, updateConfig }) => {
|
'astro:config:setup': ({ config, updateConfig, injectScript }) => {
|
||||||
|
if (analytics) {
|
||||||
|
injectScript('page', 'import "@astrojs/vercel/analytics"');
|
||||||
|
}
|
||||||
const outDir = getVercelOutput(config.root);
|
const outDir = getVercelOutput(config.root);
|
||||||
updateConfig({
|
updateConfig({
|
||||||
outDir,
|
outDir,
|
||||||
|
|
|
@ -9,13 +9,20 @@ function getAdapter(): AstroAdapter {
|
||||||
return { name: PACKAGE_NAME };
|
return { name: PACKAGE_NAME };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function vercelStatic(): AstroIntegration {
|
export interface VercelStaticConfig {
|
||||||
|
analytics?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function vercelStatic({ analytics }: VercelStaticConfig = {}): AstroIntegration {
|
||||||
let _config: AstroConfig;
|
let _config: AstroConfig;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/vercel',
|
name: '@astrojs/vercel',
|
||||||
hooks: {
|
hooks: {
|
||||||
'astro:config:setup': ({ config }) => {
|
'astro:config:setup': ({ config, injectScript }) => {
|
||||||
|
if (analytics) {
|
||||||
|
injectScript('page', 'import "@astrojs/vercel/analytics"');
|
||||||
|
}
|
||||||
config.outDir = new URL('./static/', getVercelOutput(config.root));
|
config.outDir = new URL('./static/', getVercelOutput(config.root));
|
||||||
config.build.format = 'directory';
|
config.build.format = 'directory';
|
||||||
},
|
},
|
||||||
|
|
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
|
@ -3335,6 +3335,7 @@ importers:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@astrojs/webapi': ^2.0.0
|
'@astrojs/webapi': ^2.0.0
|
||||||
'@types/set-cookie-parser': ^2.4.2
|
'@types/set-cookie-parser': ^2.4.2
|
||||||
|
'@vercel/analytics': ^0.1.8
|
||||||
'@vercel/nft': ^0.22.1
|
'@vercel/nft': ^0.22.1
|
||||||
astro: workspace:*
|
astro: workspace:*
|
||||||
astro-scripts: workspace:*
|
astro-scripts: workspace:*
|
||||||
|
@ -3342,11 +3343,14 @@ importers:
|
||||||
fast-glob: ^3.2.11
|
fast-glob: ^3.2.11
|
||||||
mocha: ^9.2.2
|
mocha: ^9.2.2
|
||||||
set-cookie-parser: ^2.5.1
|
set-cookie-parser: ^2.5.1
|
||||||
|
web-vitals: ^3.1.1
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/webapi': link:../../webapi
|
'@astrojs/webapi': link:../../webapi
|
||||||
|
'@vercel/analytics': 0.1.8
|
||||||
'@vercel/nft': 0.22.6
|
'@vercel/nft': 0.22.6
|
||||||
fast-glob: 3.2.12
|
fast-glob: 3.2.12
|
||||||
set-cookie-parser: 2.5.1
|
set-cookie-parser: 2.5.1
|
||||||
|
web-vitals: 3.1.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/set-cookie-parser': 2.4.2
|
'@types/set-cookie-parser': 2.4.2
|
||||||
astro: link:../../astro
|
astro: link:../../astro
|
||||||
|
@ -7623,6 +7627,15 @@ packages:
|
||||||
'@unocss/scope': 0.15.6
|
'@unocss/scope': 0.15.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@vercel/analytics/0.1.8:
|
||||||
|
resolution: {integrity: sha512-PQrOI8BJ9qUiVJuQfnKiJd15eDjDJH9TBKsNeMrtelT4NAk7d9mBVz1CoZkvoFnHQ0OW7Xnqmr1F2nScfAnznQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8||^17||^18
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vercel/nft/0.22.6:
|
/@vercel/nft/0.22.6:
|
||||||
resolution: {integrity: sha512-gTsFnnT4mGxodr4AUlW3/urY+8JKKB452LwF3m477RFUJTAaDmcz2JqFuInzvdybYIeyIv1sSONEJxsxnbQ5JQ==}
|
resolution: {integrity: sha512-gTsFnnT4mGxodr4AUlW3/urY+8JKKB452LwF3m477RFUJTAaDmcz2JqFuInzvdybYIeyIv1sSONEJxsxnbQ5JQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
@ -15398,6 +15411,10 @@ packages:
|
||||||
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
/web-vitals/3.1.1:
|
||||||
|
resolution: {integrity: sha512-qvllU+ZeQChqzBhZ1oyXmWsjJ8a2jHYpH8AMaVuf29yscOPZfTQTjQFRX6+eADTdsDE8IanOZ0cetweHMs8/2A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/webidl-conversions/3.0.1:
|
/webidl-conversions/3.0.1:
|
||||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue