0
Fork 0
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:
Justin Sun 2023-02-09 00:32:20 +08:00 committed by GitHub
parent ec2f2a31de
commit 23c60cfa45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 7 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/vercel': minor
---
Add vercel analytics support

View file

@ -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 Vercels 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';

View file

@ -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"

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

View file

@ -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,

View file

@ -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,

View file

@ -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
View file

@ -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==}