diff --git a/.changeset/few-cycles-trade.md b/.changeset/few-cycles-trade.md new file mode 100644 index 000000000..0b102c0c0 --- /dev/null +++ b/.changeset/few-cycles-trade.md @@ -0,0 +1,5 @@ +--- +'@astrojs/vercel': minor +--- + +Support `vercel.json` redirects and rewrites using Build Output API diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json index 7d1fda41d..8be0f95ce 100644 --- a/packages/integrations/vercel/package.json +++ b/packages/integrations/vercel/package.json @@ -48,6 +48,7 @@ "@astrojs/webapi": "^2.1.0", "@vercel/analytics": "^0.1.8", "@vercel/nft": "^0.22.1", + "@vercel/routing-utils": "^2.1.10", "fast-glob": "^3.2.11", "set-cookie-parser": "^2.5.1", "web-vitals": "^3.1.1" diff --git a/packages/integrations/vercel/src/edge/adapter.ts b/packages/integrations/vercel/src/edge/adapter.ts index 411717a41..ffc9eee30 100644 --- a/packages/integrations/vercel/src/edge/adapter.ts +++ b/packages/integrations/vercel/src/edge/adapter.ts @@ -11,6 +11,7 @@ import { removeDir, writeJson, } from '../lib/fs.js'; +import { getRoutesFromVercelJSON } from '../lib/vercel-config.js'; import { getRedirects } from '../lib/redirects.js'; const PACKAGE_NAME = '@astrojs/vercel/edge'; @@ -119,11 +120,14 @@ export default function vercelEdge({ entrypoint: relativePath(commonAncestor, entryPath), }); + const userRoutes = await getRoutesFromVercelJSON(_config) + // Output configuration // https://vercel.com/docs/build-output-api/v3#build-output-configuration await writeJson(new URL(`./config.json`, _config.outDir), { version: 3, routes: [ + ...userRoutes, ...getRedirects(routes, _config), { handle: 'filesystem' }, { src: '/.*', dest: 'render' }, diff --git a/packages/integrations/vercel/src/lib/redirects.ts b/packages/integrations/vercel/src/lib/redirects.ts index c11d74802..ff4b59ad5 100644 --- a/packages/integrations/vercel/src/lib/redirects.ts +++ b/packages/integrations/vercel/src/lib/redirects.ts @@ -1,14 +1,5 @@ import type { AstroConfig, RouteData, RoutePart } from 'astro'; - -// https://vercel.com/docs/project-configuration#legacy/routes -interface VercelRoute { - src: string; - methods?: string[]; - dest?: string; - headers?: Record; - status?: number; - continue?: boolean; -} +import type { Route as VercelRoute } from '@vercel/routing-utils'; // Copied from /home/juanm04/dev/misc/astro/packages/astro/src/core/routing/manifest/create.ts // 2022-04-26 diff --git a/packages/integrations/vercel/src/lib/vercel-config.ts b/packages/integrations/vercel/src/lib/vercel-config.ts new file mode 100644 index 000000000..2f62734fd --- /dev/null +++ b/packages/integrations/vercel/src/lib/vercel-config.ts @@ -0,0 +1,16 @@ +import type { AstroConfig } from 'astro'; +import type { Route as VercelRoute } from '@vercel/routing-utils'; +import * as fs from "node:fs/promises"; +import { getTransformedRoutes } from '@vercel/routing-utils'; + +export async function getRoutesFromVercelJSON(config: AstroConfig): Promise { + let vercelConfig: Record | undefined; + try { + vercelConfig = await fs.readFile(new URL('./vercel.json', config.root), { encoding: 'utf-8' }).then(res => JSON.parse(res)); + } catch (e) {} + + if (!vercelConfig) return [] + const { routes } = getTransformedRoutes(vercelConfig); + + return routes ?? []; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4bc8ea4d..824e6b7a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3599,6 +3599,7 @@ importers: '@types/set-cookie-parser': ^2.4.2 '@vercel/analytics': ^0.1.8 '@vercel/nft': ^0.22.1 + '@vercel/routing-utils': ^2.1.10 astro: workspace:* astro-scripts: workspace:* chai: ^4.3.6 @@ -3610,6 +3611,7 @@ importers: '@astrojs/webapi': link:../../webapi '@vercel/analytics': 0.1.8 '@vercel/nft': 0.22.6 + '@vercel/routing-utils': 2.1.10 fast-glob: 3.2.12 set-cookie-parser: 2.5.1 web-vitals: 3.1.1 @@ -8090,6 +8092,14 @@ packages: - supports-color dev: false + /@vercel/routing-utils/2.1.10: + resolution: {integrity: sha512-eOde+TQBcGOpEMd5EVTE36rK5NQOewEepEUCX1jrSgwbf/vzYP5B82zwcGdtddxEwzWqJV4QjltS6H0GZOgf6A==} + dependencies: + path-to-regexp: 6.1.0 + optionalDependencies: + ajv: 6.12.6 + dev: false + /@vitejs/plugin-vue-jsx/3.0.0_vite@4.1.2+vue@3.2.47: resolution: {integrity: sha512-vurkuzgac5SYuxd2HUZqAFAWGTF10diKBwJNbCvnWijNZfXd+7jMtqjPFbGt7idOJUn584fP1Ar9j/GN2jQ3Ew==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8346,7 +8356,6 @@ packages: fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - dev: true /ajv/8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} @@ -11778,7 +11787,6 @@ packages: /json-schema-traverse/0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true /json-schema-traverse/1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -13458,6 +13466,10 @@ packages: /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-to-regexp/6.1.0: + resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} + dev: false + /path-to-regexp/6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} @@ -15007,6 +15019,7 @@ packages: /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + requiresBuild: true /source-map/0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}