├── README.md
├── .npmrc
├── examples
├── demo
│ ├── tests
│ │ ├── common
│ │ │ ├── index.ts
│ │ │ ├── index2.ts
│ │ │ ├── index.html
│ │ │ ├── teardown.ts
│ │ │ ├── setup.ts
│ │ │ ├── utils.ts
│ │ │ └── helpers.ts
│ │ ├── 05-vike
│ │ │ ├── vitest.config.ts
│ │ │ ├── vite.config._test_.js
│ │ │ ├── utils.ts
│ │ │ ├── vc-config.test.ts
│ │ │ ├── prerender.test.ts
│ │ │ ├── fs.test.ts
│ │ │ └── config.test.ts
│ │ ├── 04-isr
│ │ │ ├── vitest.config.ts
│ │ │ ├── fs.test.ts
│ │ │ ├── globalSetup.ts
│ │ │ ├── prerender.test.ts
│ │ │ ├── vc-config.test.ts
│ │ │ └── config.test.ts
│ │ ├── 01-minimal
│ │ │ ├── vitest.config.ts
│ │ │ ├── globalSetup.ts
│ │ │ ├── fs.test.ts
│ │ │ ├── vc-config.test.ts
│ │ │ └── config.test.ts
│ │ ├── 03-prerender
│ │ │ ├── vitest.config.ts
│ │ │ ├── fs.test.ts
│ │ │ ├── globalSetup.ts
│ │ │ ├── vc-config.test.ts
│ │ │ └── config.test.ts
│ │ └── 02-additional-endpoints
│ │ │ ├── vitest.config.ts
│ │ │ ├── fs.test.ts
│ │ │ ├── globalSetup.ts
│ │ │ ├── vc-config.test.ts
│ │ │ └── config.test.ts
│ ├── pages
│ │ ├── named
│ │ │ ├── +route.ts
│ │ │ ├── +onBeforePrerenderStart.ts
│ │ │ ├── +config.ts
│ │ │ ├── +onBeforeRender.ts
│ │ │ └── +Page.tsx
│ │ ├── catch-all
│ │ │ ├── +route.ts
│ │ │ ├── +onBeforePrerenderStart.ts
│ │ │ ├── +config.ts
│ │ │ ├── +onBeforeRender.ts
│ │ │ └── +Page.tsx
│ │ ├── dynamic
│ │ │ ├── +config.ts
│ │ │ ├── +onBeforeRender.ts
│ │ │ └── +Page.tsx
│ │ ├── function
│ │ │ ├── +onBeforePrerenderStart.ts
│ │ │ ├── +config.ts
│ │ │ ├── +route.ts
│ │ │ ├── +onBeforeRender.ts
│ │ │ └── +Page.tsx
│ │ ├── isr
│ │ │ ├── +config.ts
│ │ │ ├── +onBeforeRender.ts
│ │ │ └── +Page.tsx
│ │ ├── vike-edge
│ │ │ ├── +config.ts
│ │ │ ├── Counter.tsx
│ │ │ └── +Page.tsx
│ │ ├── static
│ │ │ ├── +onBeforeRender.ts
│ │ │ └── +Page.tsx
│ │ ├── index
│ │ │ ├── Counter.tsx
│ │ │ └── +Page.tsx
│ │ └── _error
│ │ │ └── +Page.tsx
│ ├── endpoints
│ │ ├── Roboto-Regular.ttf
│ │ ├── edge.ts
│ │ ├── og-edge.tsx
│ │ └── og-node.tsx
│ ├── renderer
│ │ ├── css
│ │ │ ├── reset.css
│ │ │ ├── code.css
│ │ │ ├── index.css
│ │ │ ├── links.css
│ │ │ ├── loading.svg
│ │ │ └── page-transition-loading-animation.css
│ │ ├── +onHydrationEnd.ts
│ │ ├── +onPageTransitionEnd.ts
│ │ ├── +onPageTransitionStart.ts
│ │ ├── Link.tsx
│ │ ├── getPageTitle.ts
│ │ ├── usePageContext.tsx
│ │ ├── +onRenderClient.tsx
│ │ ├── +onRenderHtml.tsx
│ │ ├── types.ts
│ │ ├── +config.ts
│ │ ├── PageShell.tsx
│ │ └── logo.svg
│ ├── public
│ │ └── test.html
│ ├── _api
│ │ ├── post.ts
│ │ ├── name
│ │ │ └── [name].ts
│ │ └── page.ts
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── package.json
│ └── CHANGELOG.md
├── simple
│ ├── _api
│ │ ├── endpoint.ts
│ │ ├── [...catchAll].ts
│ │ ├── [[...optionalCatchAll]].ts
│ │ ├── edge.ts
│ │ ├── isr.ts
│ │ └── headers.ts
│ ├── index.html
│ ├── vite.config.ts
│ ├── tsconfig.json
│ ├── package.json
│ └── CHANGELOG.md
└── express
│ ├── pages
│ ├── +config.ts
│ └── index
│ │ ├── Counter.tsx
│ │ └── +Page.tsx
│ ├── tsconfig.json
│ ├── server
│ └── vike-handler.ts
│ ├── vite.config.ts
│ ├── package.json
│ ├── express-entry.ts
│ └── CHANGELOG.md
├── packages
├── vike-integration
│ ├── config.js
│ ├── helpers.js
│ ├── helpers.d.ts
│ ├── tsconfig.json
│ ├── +config.ts
│ ├── config.d.ts
│ ├── templates
│ │ ├── utils.ts
│ │ ├── ssr_edge_.template.ts
│ │ ├── ssr_.template.ts
│ │ ├── node-helpers.ts
│ │ └── edge-helpers.ts
│ ├── route-regex.ts
│ ├── tsup.config.ts
│ ├── package.json
│ ├── README.md
│ └── CHANGELOG.md
└── vercel
│ ├── index.d.ts
│ ├── src
│ ├── schemas
│ │ ├── exports.ts
│ │ └── config
│ │ │ ├── prerender-config.ts
│ │ │ ├── vc-config.ts
│ │ │ └── config.ts
│ ├── assert.ts
│ ├── helpers.ts
│ ├── utils.ts
│ ├── prerender.ts
│ ├── config.ts
│ ├── index.ts
│ ├── types.ts
│ └── build.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ ├── package.json
│ ├── CHANGELOG.md
│ └── README.md
├── pnpm-workspace.yaml
├── .vscode
└── settings.json
├── vercel.json
├── tsconfig.json
├── .changeset
├── config.json
└── README.md
├── biome.json
├── .github
├── renovate.json5
└── workflows
│ └── node.js.yml
├── package.json
└── .gitignore
/README.md:
--------------------------------------------------------------------------------
1 | packages/vercel/README.md
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-manager-strict=false
2 |
3 |
--------------------------------------------------------------------------------
/examples/demo/tests/common/index.ts:
--------------------------------------------------------------------------------
1 | // SSR entrypoint
2 |
--------------------------------------------------------------------------------
/examples/demo/pages/named/+route.ts:
--------------------------------------------------------------------------------
1 | export default "/named/@someId";
2 |
--------------------------------------------------------------------------------
/examples/demo/pages/catch-all/+route.ts:
--------------------------------------------------------------------------------
1 | export default "/catch-all/*";
2 |
--------------------------------------------------------------------------------
/packages/vike-integration/config.js:
--------------------------------------------------------------------------------
1 | export * from "./dist/+config.js";
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
3 | - 'examples/*'
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/demo/tests/common/index2.ts:
--------------------------------------------------------------------------------
1 | // SSR entrypoint
2 | console.log("hello");
3 |
--------------------------------------------------------------------------------
/packages/vike-integration/helpers.js:
--------------------------------------------------------------------------------
1 | export * from "./dist/templates/helpers.js";
2 |
--------------------------------------------------------------------------------
/examples/demo/pages/dynamic/+config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | prerender: false,
3 | };
4 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "redirects": [{ "source": "/redirect", "destination": "/static" }]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/vike-integration/helpers.d.ts:
--------------------------------------------------------------------------------
1 | // Help TS's resolver for node10 target
2 | export * from "./dist/templates/helpers";
3 |
--------------------------------------------------------------------------------
/examples/demo/pages/function/+onBeforePrerenderStart.ts:
--------------------------------------------------------------------------------
1 | export default function prerender() {
2 | return ["/function/a"];
3 | }
4 |
--------------------------------------------------------------------------------
/examples/demo/endpoints/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magne4000/vite-plugin-vercel/HEAD/examples/demo/endpoints/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/examples/demo/pages/named/+onBeforePrerenderStart.ts:
--------------------------------------------------------------------------------
1 | export default function prerender() {
2 | return ["/named/id-1", "/named/id-2"];
3 | }
4 |
--------------------------------------------------------------------------------
/examples/demo/renderer/css/reset.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: sans-serif;
4 | }
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/demo/pages/named/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | export default {
4 | isr: true,
5 | } satisfies Config;
6 |
--------------------------------------------------------------------------------
/examples/demo/pages/catch-all/+onBeforePrerenderStart.ts:
--------------------------------------------------------------------------------
1 | export default function prerender() {
2 | return ["/catch-all/a/b/c", "/catch-all/a/d"];
3 | }
4 |
--------------------------------------------------------------------------------
/examples/demo/pages/catch-all/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | export default {
4 | isr: { expiration: 15 },
5 | } satisfies Config;
6 |
--------------------------------------------------------------------------------
/examples/demo/renderer/css/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | background-color: #eaeaea;
4 | padding: 3px 5px;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/demo/renderer/css/index.css:
--------------------------------------------------------------------------------
1 | @import "./reset.css";
2 | @import "./links.css";
3 | @import "./code.css";
4 | @import "./page-transition-loading-animation.css";
5 |
--------------------------------------------------------------------------------
/examples/demo/pages/isr/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | export default {
4 | isr: { expiration: 15 },
5 | prerender: false,
6 | } satisfies Config;
7 |
--------------------------------------------------------------------------------
/examples/demo/renderer/+onHydrationEnd.ts:
--------------------------------------------------------------------------------
1 | export default onHydrationEnd;
2 |
3 | function onHydrationEnd() {
4 | console.log("Hydration finished; page is now interactive.");
5 | }
6 |
--------------------------------------------------------------------------------
/examples/demo/public/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/demo/renderer/css/links.css:
--------------------------------------------------------------------------------
1 | a {
2 | text-decoration: none;
3 | }
4 | #sidebar a {
5 | padding: 2px 10px;
6 | margin-left: -10px;
7 | }
8 | #sidebar a.is-active {
9 | background-color: #eee;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/demo/tests/05-vike/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | coverage: {
6 | enabled: false,
7 | },
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/examples/demo/_api/post.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export default async function handler(request: VercelRequest, response: VercelResponse) {
4 | return response.send("OK");
5 | }
6 |
--------------------------------------------------------------------------------
/examples/simple/_api/endpoint.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export default async function handler(request: VercelRequest, response: VercelResponse) {
4 | return response.send("OK");
5 | }
6 |
--------------------------------------------------------------------------------
/examples/demo/pages/function/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | export default {
4 | // Should warn when building because it's incompatible with route function
5 | isr: { expiration: 15 },
6 | } satisfies Config;
7 |
--------------------------------------------------------------------------------
/examples/simple/_api/[...catchAll].ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export default async function handler(request: VercelRequest, response: VercelResponse) {
4 | return response.send("OK");
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Bundler",
6 | "target": "ES2022",
7 | "lib": ["ESNext"],
8 | "esModuleInterop": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/demo/pages/vike-edge/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | export default {
4 | prerender: false,
5 | edge: true,
6 | headers: {
7 | "X-VitePluginVercel-Test": "test",
8 | },
9 | } satisfies Config;
10 |
--------------------------------------------------------------------------------
/examples/demo/renderer/+onPageTransitionEnd.ts:
--------------------------------------------------------------------------------
1 | export default onPageTransitionEnd;
2 |
3 | function onPageTransitionEnd() {
4 | console.log("Page transition end");
5 | document.querySelector("body")?.classList.remove("page-is-transitioning");
6 | }
7 |
--------------------------------------------------------------------------------
/examples/simple/_api/[[...optionalCatchAll]].ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export default async function handler(request: VercelRequest, response: VercelResponse) {
4 | return response.send("OK");
5 | }
6 |
--------------------------------------------------------------------------------
/examples/demo/renderer/+onPageTransitionStart.ts:
--------------------------------------------------------------------------------
1 | export default onPageTransitionStart;
2 |
3 | function onPageTransitionStart() {
4 | console.log("Page transition start");
5 | document.querySelector("body")?.classList.add("page-is-transitioning");
6 | }
7 |
--------------------------------------------------------------------------------
/examples/demo/_api/name/[name].ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export default async function handler(request: VercelRequest, response: VercelResponse) {
4 | return response.send(`Name: ${request.query.name}`);
5 | }
6 |
--------------------------------------------------------------------------------
/examples/simple/_api/edge.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export const edge = true;
4 |
5 | export default async function handler(request: VercelRequest, response: VercelResponse) {
6 | return response.send("OK");
7 | }
8 |
--------------------------------------------------------------------------------
/examples/demo/pages/isr/+onBeforeRender.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onBeforeRender
2 | export default function onBeforeRender() {
3 | return {
4 | pageContext: {
5 | pageProps: {
6 | d: String(new Date()),
7 | },
8 | },
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/examples/demo/pages/static/+onBeforeRender.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onBeforeRender
2 | export default function onBeforeRender() {
3 | return {
4 | pageContext: {
5 | pageProps: {
6 | d: String(new Date()),
7 | },
8 | },
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vercel/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { ViteVercelConfig } from "./dist";
2 |
3 | export * from "./dist";
4 | export { default } from "./dist";
5 |
6 | declare module "vite" {
7 | export interface UserConfig {
8 | vercel?: ViteVercelConfig;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/demo/tests/04-isr/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | globalSetup: "tests/04-isr/globalSetup.ts",
6 | coverage: {
7 | enabled: false,
8 | },
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/examples/express/pages/+config.ts:
--------------------------------------------------------------------------------
1 | import vikeReact from "vike-react/config";
2 | import type { Config } from "vike/types";
3 |
4 | // Default config (can be overridden by pages)
5 | export default {
6 | title: "My Vike App",
7 | extends: vikeReact,
8 | } satisfies Config;
9 |
--------------------------------------------------------------------------------
/examples/simple/_api/isr.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export const isr = { expiration: 10 };
4 |
5 | export default async function handler(request: VercelRequest, response: VercelResponse) {
6 | return response.send("OK");
7 | }
8 |
--------------------------------------------------------------------------------
/examples/demo/tests/01-minimal/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | globalSetup: "tests/01-minimal/globalSetup.ts",
6 | coverage: {
7 | enabled: false,
8 | },
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/examples/demo/endpoints/edge.ts:
--------------------------------------------------------------------------------
1 | import { get } from "@vercel/edge-config";
2 |
3 | export const edge = true;
4 |
5 | export default async function handler() {
6 | await get("someKey");
7 |
8 | return new Response("Edge Function: OK", {
9 | status: 200,
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/examples/demo/tests/03-prerender/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | globalSetup: "tests/03-prerender/globalSetup.ts",
6 | coverage: {
7 | enabled: false,
8 | },
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/examples/demo/pages/function/+route.ts:
--------------------------------------------------------------------------------
1 | import type { PageContextBuiltInServer } from "vike/types";
2 |
3 | export default function (pageContext: PageContextBuiltInServer) {
4 | if (!pageContext.urlPathname.startsWith("/function/")) return false;
5 | return {
6 | precedence: -1,
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/examples/simple/_api/headers.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export const headers = { "X-VitePluginVercel-Test": "test" };
4 |
5 | export default async function handler(request: VercelRequest, response: VercelResponse) {
6 | return response.send("OK");
7 | }
8 |
--------------------------------------------------------------------------------
/examples/demo/tests/02-additional-endpoints/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | globalSetup: "tests/02-additional-endpoints/globalSetup.ts",
6 | coverage: {
7 | enabled: false,
8 | },
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/examples/demo/_api/page.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 |
3 | export const headers = {
4 | "X-VitePluginVercel-Test": "test",
5 | };
6 |
7 | export default async function handler(request: VercelRequest, response: VercelResponse) {
8 | return response.send("OK");
9 | }
10 |
--------------------------------------------------------------------------------
/examples/demo/pages/catch-all/+onBeforeRender.ts:
--------------------------------------------------------------------------------
1 | import { PageContextBuiltInServer } from "vike/types";
2 |
3 | // https://vike.dev/onBeforeRender
4 | export default function onBeforeRender() {
5 | return {
6 | pageContext: {
7 | pageProps: {
8 | d: String(new Date()),
9 | },
10 | },
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/examples/demo/pages/dynamic/+onBeforeRender.ts:
--------------------------------------------------------------------------------
1 | import { PageContextBuiltInServer } from "vike/types";
2 |
3 | // https://vike.dev/onBeforeRender
4 | export default function onBeforeRender() {
5 | return {
6 | pageContext: {
7 | pageProps: {
8 | d: String(new Date()),
9 | },
10 | },
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/examples/demo/pages/function/+onBeforeRender.ts:
--------------------------------------------------------------------------------
1 | import { PageContextBuiltInServer } from "vike/types";
2 |
3 | // https://vike.dev/onBeforeRender
4 | export default function onBeforeRender() {
5 | return {
6 | pageContext: {
7 | pageProps: {
8 | d: String(new Date()),
9 | },
10 | },
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/examples/demo/pages/index/Counter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | export { Counter };
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0);
7 | return (
8 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/demo/pages/vike-edge/Counter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | export { Counter };
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0);
7 | return (
8 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/express/pages/index/Counter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | export { Counter };
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0);
7 | return (
8 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | My App
7 |
8 |
9 | Created with Bâti
10 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/vike-integration/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "lib": ["ES2022"],
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2022",
5 | "moduleResolution": "Bundler",
6 | "target": "ES2022",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react-jsx",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/vercel/src/schemas/exports.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const vercelEndpointExports = z.object({
4 | edge: z.boolean().optional(),
5 | headers: z.record(z.string()).optional(),
6 | streaming: z.boolean().optional(),
7 | isr: z
8 | .object({
9 | expiration: z.number().or(z.literal(false)),
10 | })
11 | .optional(),
12 | });
13 |
--------------------------------------------------------------------------------
/examples/demo/pages/isr/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Page(props: { d: string }) {
4 | return (
5 | <>
6 | Welcome
7 | This page is:
8 |
9 | - ISR
10 | - ISR: regenerated after {15} seconds
11 | - {props.d}
12 |
13 | >
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vike-integration/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | export default {
4 | name: "@vite-plugin-vercel/vike",
5 | meta: {
6 | isr: {
7 | env: { server: true },
8 | },
9 | edge: {
10 | env: { server: true },
11 | },
12 | headers: {
13 | env: { server: true },
14 | },
15 | },
16 | } satisfies Config;
17 |
--------------------------------------------------------------------------------
/examples/simple/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import vercel from "vite-plugin-vercel";
3 |
4 | export default defineConfig({
5 | plugins: [
6 | vercel({
7 | // `smart` param only exist to circumvent a pnpm issue in this repo
8 | // You should not use this parameter outside this repository
9 | smart: false,
10 | }),
11 | ],
12 | });
13 |
--------------------------------------------------------------------------------
/packages/vercel/src/assert.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import { newError } from "@brillout/libassert";
3 |
4 | const libName = "vite-plugin-vercel";
5 |
6 | export function assert(condition: unknown, errorMessage: string): asserts condition {
7 | if (condition) {
8 | return;
9 | }
10 |
11 | const err = newError(`[${libName}][Wrong Usage] ${errorMessage}`, 2);
12 | throw err;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/demo/pages/static/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Page(props: { d: string }) {
4 | return (
5 | <>
6 | Welcome
7 | This page is:
8 |
9 | - Static
10 | - Static html generated
11 | - No ISR
12 | - {props.d}
13 |
14 | >
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/examples/demo/pages/dynamic/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Page(props: { d: string }) {
4 | return (
5 | <>
6 | Welcome
7 | This page is:
8 |
9 | - Dynamic
10 | - No static html generated
11 | - No ISR
12 | - {props.d}
13 |
14 | >
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/examples/demo/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Counter } from "./Counter.js";
3 |
4 | export default function Page() {
5 | return (
6 | <>
7 | Welcome
8 | This page is:
9 |
10 | - Rendered to HTML.
11 | -
12 | Interactive.
13 |
14 |
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/demo/tests/common/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 | Document
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/demo/pages/vike-edge/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Counter } from "./Counter.js";
3 |
4 | export default function Page() {
5 | return (
6 | <>
7 | Welcome
8 | This page is:
9 |
10 | - Rendered to HTML.
11 | -
12 | Interactive.
13 |
14 |
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/express/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Counter } from "./Counter.js";
3 |
4 | export default function Page() {
5 | return (
6 | <>
7 | Welcome
8 | This page is:
9 |
10 | - Rendered to HTML.
11 | -
12 | Interactive.
13 |
14 |
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/demo/tests/common/teardown.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import os from "node:os";
3 | import fs from "node:fs/promises";
4 |
5 | export function teardown(displayName: string) {
6 | return async () => {
7 | const tmpdir = path.join(os.tmpdir(), `vpv-demo-${displayName}`);
8 |
9 | await fs.rm(tmpdir, {
10 | recursive: true,
11 | force: true,
12 | });
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vercel/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2021",
4 | "lib": ["es2021", "dom"],
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "paths": {
12 | "vite": ["./node_modules/vite"]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/demo/pages/named/+onBeforeRender.ts:
--------------------------------------------------------------------------------
1 | import type { PageContextBuiltInServer } from "vike/types";
2 |
3 | // https://vike.dev/onBeforeRender
4 | export default function onBeforeRender(pageContext: PageContextBuiltInServer) {
5 | return {
6 | pageContext: {
7 | pageProps: {
8 | d: String(new Date()),
9 | someId: pageContext.routeParams.someId,
10 | },
11 | },
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/packages/vercel/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | {
5 | entry: ["./src/index.ts"],
6 | external: ["esbuild", "vike"],
7 | format: ["esm", "cjs"],
8 | platform: "node",
9 | target: "node18",
10 | dts: {
11 | entry: "./src/index.ts",
12 | compilerOptions: {
13 | paths: {},
14 | },
15 | },
16 | },
17 | ]);
18 |
--------------------------------------------------------------------------------
/examples/express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "esModuleInterop": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "resolveJsonModule": true,
7 | "skipLibCheck": true,
8 | "sourceMap": true,
9 | "module": "ESNext",
10 | "noEmit": true,
11 | "moduleResolution": "Bundler",
12 | "target": "ES2022",
13 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
14 | "types": ["vite/client"],
15 | "jsx": "react-jsx"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "bumpVersionsWithWorkspaceProtocolOnly": true,
11 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
12 | "onlyUpdatePeerDependentsWhenOutOfRange": true
13 | },
14 | "ignore": []
15 | }
16 |
--------------------------------------------------------------------------------
/examples/simple/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "module": "ESNext",
12 | "noEmit": true,
13 | "moduleResolution": "Bundler",
14 | "target": "ES2022",
15 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
16 | "types": ["vite/client"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/examples/demo/renderer/css/loading.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/packages/vike-integration/config.d.ts:
--------------------------------------------------------------------------------
1 | import "vike/types";
2 |
3 | // When this is imported by a projet ->
4 | // import config from '@vite-plugin-vercel/vike/config'
5 | // the following override is applied to the whole project
6 | declare global {
7 | namespace Vike {
8 | export interface Config {
9 | isr?: boolean | { expiration: number };
10 | edge?: boolean;
11 | headers?: Record;
12 | }
13 | }
14 | }
15 |
16 | // Help TS's resolver for node10 target
17 | export * from "./dist/+config.h";
18 |
--------------------------------------------------------------------------------
/packages/vercel/src/helpers.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import path from "node:path";
3 |
4 | export async function copyDir(src: string, dest: string) {
5 | await fs.mkdir(dest, { recursive: true });
6 | const entries = await fs.readdir(src, { withFileTypes: true });
7 |
8 | for (const entry of entries) {
9 | const srcPath = path.join(src, entry.name);
10 | const destPath = path.join(dest, entry.name);
11 |
12 | entry.isDirectory() ? await copyDir(srcPath, destPath) : await fs.copyFile(srcPath, destPath);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/demo/renderer/Link.tsx:
--------------------------------------------------------------------------------
1 | export { Link };
2 |
3 | import { usePageContext } from "./usePageContext";
4 | import React from "react";
5 |
6 | function Link({ href, children }: { href: string; children: string }) {
7 | const pageContext = usePageContext() as { urlPathname: string }; // TODO
8 | const { urlPathname } = pageContext;
9 | const isActive = href === "/" ? urlPathname === href : urlPathname.startsWith(href);
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/simple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-plugin-vercel-simple",
3 | "private": true,
4 | "version": null,
5 | "description": "",
6 | "type": "module",
7 | "scripts": {
8 | "build": "vite build",
9 | "typecheck": "tsc -p tsconfig.json --noEmit"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "devDependencies": {
14 | "@types/node": "^18.19.130",
15 | "@vercel/node": "^3.2.29",
16 | "typescript": "^5.9.3",
17 | "vite": "^6.4.1"
18 | },
19 | "dependencies": {
20 | "vite-plugin-vercel": "workspace:*"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/demo/renderer/getPageTitle.ts:
--------------------------------------------------------------------------------
1 | export { getPageTitle };
2 |
3 | import type { PageContext } from "./types";
4 |
5 | function getPageTitle(pageContext: PageContext): string {
6 | const title =
7 | // Title defined dynamically by onBeforeRender()
8 | pageContext.title ||
9 | // Title defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js)
10 | // The config 'pageContext.config.title' is a custom config we defined at ./+config.ts
11 | pageContext.config.title ||
12 | "Demo";
13 | return title;
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vike-integration/templates/utils.ts:
--------------------------------------------------------------------------------
1 | import { parse } from "qs";
2 |
3 | export function getOriginalUrl(xNowRouteMatchesHeader: unknown, originalPath: unknown, url?: string) {
4 | const matches =
5 | // FIXME x-now-route-matches is not definitive https://github.com/orgs/vercel/discussions/577#discussioncomment-2769478
6 | typeof xNowRouteMatchesHeader === "string" ? parse(xNowRouteMatchesHeader) : null;
7 | return typeof originalPath === "string"
8 | ? originalPath
9 | : matches && typeof matches?.["1"] === "string"
10 | ? matches["1"]
11 | : (url ?? "");
12 | }
13 |
--------------------------------------------------------------------------------
/examples/demo/tests/01-minimal/globalSetup.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { setup as _setup } from "../common/setup";
3 | import { teardown as _teardown } from "../common/teardown";
4 | import react from "@vitejs/plugin-react-swc";
5 | import vercel from "vite-plugin-vercel";
6 |
7 | export const setup = _setup(path.basename(__dirname), {
8 | configFile: false,
9 | mode: "production",
10 | root: process.cwd(),
11 | plugins: [
12 | react(),
13 | vercel({
14 | smart: false,
15 | }),
16 | ],
17 | });
18 |
19 | export const teardown = _teardown(path.basename(__dirname));
20 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 | "files": {
4 | "ignore": ["dist/", "package.json"]
5 | },
6 | "formatter": {
7 | "indentWidth": 2,
8 | "indentStyle": "space"
9 | },
10 | "javascript": {
11 | "formatter": {
12 | "lineWidth": 120
13 | }
14 | },
15 | "vcs": {
16 | "enabled": true,
17 | "clientKind": "git"
18 | },
19 | "overrides": [
20 | {
21 | "include": ["**/*.test.ts"],
22 | "linter": {
23 | "rules": {
24 | "suspicious": { "noExplicitAny": "off" }
25 | }
26 | }
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/examples/demo/tests/common/setup.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import os from "node:os";
3 | import path from "node:path";
4 | import type { InlineConfig } from "vite";
5 | import { callBuild } from "./utils";
6 |
7 | export function setup(displayName: string, inlineConfig: InlineConfig) {
8 | return async () => {
9 | const tmpdir = path.join(os.tmpdir(), `vpv-demo-${displayName}`);
10 |
11 | await fs.rm(tmpdir, {
12 | recursive: true,
13 | force: true,
14 | });
15 | await fs.mkdir(tmpdir, {
16 | recursive: true,
17 | });
18 |
19 | await callBuild(displayName, inlineConfig);
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/examples/demo/renderer/usePageContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import type { PageContext } from "./types";
3 |
4 | export { PageContextProvider };
5 | export { usePageContext };
6 |
7 | const Context = React.createContext(undefined as unknown as PageContext);
8 |
9 | function PageContextProvider({
10 | pageContext,
11 | children,
12 | }: {
13 | pageContext: PageContext;
14 | children: React.ReactNode;
15 | }) {
16 | return {children};
17 | }
18 |
19 | function usePageContext() {
20 | const pageContext = useContext(Context);
21 | return pageContext;
22 | }
23 |
--------------------------------------------------------------------------------
/examples/demo/tests/03-prerender/fs.test.ts:
--------------------------------------------------------------------------------
1 | import { testFs } from "../common/helpers";
2 | import { describe } from "vitest";
3 | import path from "node:path";
4 |
5 | describe("fs", () => {
6 | testFs(path.basename(__dirname), [
7 | "/config.json",
8 | "/functions/api/name/[name].func/.vc-config.json",
9 | "/functions/api/name/[name].func/index.mjs",
10 | "/functions/api/page.func/index.mjs",
11 | "/functions/api/page.func/.vc-config.json",
12 | "/functions/api/post.func/index.mjs",
13 | "/functions/api/post.func/.vc-config.json",
14 | "/functions/edge.func/index.js",
15 | "/functions/edge.func/.vc-config.json",
16 | ]);
17 | });
18 |
--------------------------------------------------------------------------------
/examples/demo/tests/05-vike/vite.config._test_.js:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react";
2 | import ssr from "vike/plugin";
3 | import vercel from "vite-plugin-vercel";
4 |
5 | export default {
6 | mode: "production",
7 | root: process.cwd(),
8 | plugins: [
9 | react(),
10 | ssr({
11 | prerender: {
12 | disableAutoRun: true,
13 | },
14 | }),
15 | vercel(),
16 | ],
17 | vercel: {
18 | rewrites: [],
19 | additionalEndpoints: [
20 | {
21 | source: "endpoints/edge.ts",
22 | destination: "edge",
23 | edge: true,
24 | route: true,
25 | },
26 | ],
27 | expiration: 25,
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | // Inspired by https://github.com/vitejs/vite/blob/main/.github/renovate.json5
3 | $schema: 'https://docs.renovatebot.com/renovate-schema.json',
4 | extends: [
5 | 'config:base',
6 | 'schedule:weekly',
7 | 'group:allNonMajor',
8 | // peerDependencies ranges should be left untouched/wide.
9 | ':disablePeerDependencies'
10 | ],
11 | labels: [
12 | 'dependencies'
13 | ],
14 | rangeStrategy: 'bump',
15 | assignees: [
16 | '@magne4000'
17 | ],
18 | ignoreDeps: [
19 | // manually bumping
20 | 'node',
21 | '@types/node',
22 | 'pnpm',
23 | ],
24 | postUpdateOptions: [
25 | 'pnpmDedupe'
26 | ],
27 | }
28 |
--------------------------------------------------------------------------------
/examples/demo/renderer/css/page-transition-loading-animation.css:
--------------------------------------------------------------------------------
1 | #page-container {
2 | position: relative;
3 | width: 100%;
4 | }
5 | #page-container::before {
6 | content: "";
7 | position: absolute;
8 | width: 100%;
9 | height: 100%;
10 | z-index: 999;
11 | background: no-repeat url("./loading.svg");
12 | background-size: 100px;
13 | background-position: center center;
14 | pointer-events: none;
15 | opacity: 0;
16 | }
17 | body.page-is-transitioning #page-container::before {
18 | opacity: 1;
19 | }
20 | #page-content,
21 | #page-container::before {
22 | transition: opacity 0.5s ease-in-out;
23 | }
24 | body.page-is-transitioning #page-content {
25 | opacity: 0.17;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/vercel/src/schemas/config/prerender-config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Schema definition for `.vercel/output/config.json`
3 | * @see {@link https://vercel.com/docs/build-output-api/v3#build-output-configuration}
4 | */
5 |
6 | import { z } from "zod";
7 |
8 | export const vercelOutputPrerenderConfigSchema = z
9 | .object({
10 | expiration: z.union([z.number().int().positive(), z.literal(false)]),
11 | group: z.number().int().optional(),
12 | bypassToken: z.string().optional(),
13 | fallback: z.string().optional(),
14 | allowQuery: z.array(z.string()).optional(),
15 | })
16 | .strict();
17 |
18 | export type VercelOutputPrerenderConfig = z.infer;
19 |
--------------------------------------------------------------------------------
/examples/express/server/vike-handler.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { renderPage } from "vike/server";
3 |
4 | export async function vikeHandler>(
5 | request: Request,
6 | context?: Context,
7 | ): Promise {
8 | const pageContextInit = { ...context, urlOriginal: request.url };
9 | const pageContext = await renderPage(pageContextInit);
10 | const response = pageContext.httpResponse;
11 |
12 | const { readable, writable } = new TransformStream();
13 |
14 | response?.pipe(writable);
15 |
16 | return new Response(readable, {
17 | status: response?.statusCode,
18 | headers: response?.headers,
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/packages/vike-integration/templates/ssr_edge_.template.ts:
--------------------------------------------------------------------------------
1 | import { renderPage } from "vike/server";
2 | import { getDefaultEmptyResponseHandler, getDefaultPageContextInit, getDefaultResponseHandler } from "./edge-helpers";
3 |
4 | export default async function handler(request: Request): Promise {
5 | console.debug("url", request.url);
6 | console.debug("headers", request.headers);
7 | const pageContextInit = getDefaultPageContextInit(request);
8 | const pageContext = await renderPage(pageContextInit);
9 | const { httpResponse } = pageContext;
10 |
11 | if (!httpResponse) {
12 | return getDefaultEmptyResponseHandler();
13 | }
14 |
15 | return getDefaultResponseHandler(httpResponse);
16 | }
17 |
--------------------------------------------------------------------------------
/examples/demo/tests/01-minimal/fs.test.ts:
--------------------------------------------------------------------------------
1 | import { testFs } from "../common/helpers";
2 | import { describe } from "vitest";
3 | import path from "node:path";
4 |
5 | describe("fs", () => {
6 | testFs(path.basename(__dirname), [
7 | "/config.json",
8 | "/functions/api/name/[name].func/.vc-config.json",
9 | "/functions/api/name/[name].func/index.mjs",
10 | "/functions/api/page.func/index.mjs",
11 | "/functions/api/page.func/.vc-config.json",
12 | "/functions/api/post.func/index.mjs",
13 | "/functions/api/post.func/.vc-config.json",
14 | "/functions/edge.func/index.js",
15 | "/functions/edge.func/.vc-config.json",
16 | "/static/test.html",
17 | "/static/tests/common/index.html",
18 | ]);
19 | });
20 |
--------------------------------------------------------------------------------
/examples/demo/tests/03-prerender/globalSetup.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { setup as _setup } from "../common/setup";
3 | import { teardown as _teardown } from "../common/teardown";
4 | import react from "@vitejs/plugin-react-swc";
5 | import vercel from "vite-plugin-vercel";
6 |
7 | export const setup = _setup(path.basename(__dirname), {
8 | configFile: false,
9 | mode: "production",
10 | root: process.cwd(),
11 | plugins: [
12 | react(),
13 | vercel({
14 | smart: false,
15 | }),
16 | ],
17 | vercel: {
18 | prerender() {
19 | return {
20 | ssr: {
21 | path: "ssr_",
22 | },
23 | };
24 | },
25 | distContainsOnlyStatic: false,
26 | },
27 | });
28 |
29 | export const teardown = _teardown(path.basename(__dirname));
30 |
--------------------------------------------------------------------------------
/examples/demo/tests/04-isr/fs.test.ts:
--------------------------------------------------------------------------------
1 | import { testFs } from "../common/helpers";
2 | import { describe } from "vitest";
3 | import path from "node:path";
4 |
5 | describe("fs", () => {
6 | testFs(path.basename(__dirname), [
7 | "/config.json",
8 | "/functions/api/name/[name].func/.vc-config.json",
9 | "/functions/api/name/[name].func/index.mjs",
10 | "/functions/api/page.func/index.mjs",
11 | "/functions/api/page.func/.vc-config.json",
12 | "/functions/api/post.func/index.mjs",
13 | "/functions/api/post.func/.vc-config.json",
14 | "/functions/edge.func/index.js",
15 | "/functions/edge.func/.vc-config.json",
16 | "/functions/page1.func/index.mjs",
17 | "/functions/page1.func/.vc-config.json",
18 | "/functions/page1.prerender-config.json",
19 | ]);
20 | });
21 |
--------------------------------------------------------------------------------
/examples/express/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react-swc";
2 | import vike from "vike/plugin";
3 | import type { UserConfig } from "vite";
4 | import vercel from "vite-plugin-vercel";
5 |
6 | export default {
7 | plugins: [
8 | react(),
9 | vike(),
10 | vercel({
11 | // You usually want the server to handle all routes
12 | source: "/.*",
13 | }),
14 | ],
15 | vercel: {
16 | additionalEndpoints: [
17 | {
18 | // entry file to the server. Default export must be a node server or a function
19 | source: "express-entry.ts",
20 | // replaces default Vike target
21 | destination: "ssr_",
22 | // already added by default Vike route
23 | route: false,
24 | },
25 | ],
26 | },
27 | } as UserConfig;
28 |
--------------------------------------------------------------------------------
/examples/demo/endpoints/og-edge.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ImageResponse } from "@vercel/og";
3 |
4 | import type { VercelRequest, VercelResponse } from "@vercel/node";
5 |
6 | export const edge = true;
7 |
8 | export default async function handler(request: VercelRequest, response: VercelResponse) {
9 | return new ImageResponse(
10 |
23 | 👋 Hello
24 |
,
25 | {
26 | width: 1200,
27 | height: 630,
28 | },
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/examples/demo/tests/04-isr/globalSetup.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { setup as _setup } from "../common/setup";
3 | import { teardown as _teardown } from "../common/teardown";
4 | import react from "@vitejs/plugin-react-swc";
5 | import vercel from "vite-plugin-vercel";
6 |
7 | export const setup = _setup(path.basename(__dirname), {
8 | configFile: false,
9 | mode: "production",
10 | root: process.cwd(),
11 | plugins: [
12 | react(),
13 | vercel({
14 | smart: false,
15 | }),
16 | ],
17 | vercel: {
18 | isr: {
19 | page1: {
20 | expiration: 42,
21 | route: "/page1",
22 | symlink: "api/page",
23 | },
24 | },
25 | distContainsOnlyStatic: false,
26 | },
27 | });
28 |
29 | export const teardown = _teardown(path.basename(__dirname));
30 |
--------------------------------------------------------------------------------
/examples/demo/tests/04-isr/prerender.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputPrerenderConfigSchema } from "../../../../packages/vercel/src/schemas/config/prerender-config";
4 | import { prepareTestJsonFileContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFileContent(path.basename(__dirname), "/functions/page1.prerender-config.json", (context) => {
7 | testSchema(context, vercelOutputPrerenderConfigSchema);
8 |
9 | it("should have only necessary properties", () => {
10 | expect(Object.keys(context.file as any)).toHaveLength(2);
11 | expect(context.file).toHaveProperty("expiration", 42);
12 | expect(context.file).toHaveProperty("group");
13 | expect((context.file as any).group).toBeTypeOf("number");
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/examples/demo/pages/_error/+Page.tsx:
--------------------------------------------------------------------------------
1 | import type React from "react";
2 | import { usePageContext } from "../../renderer/usePageContext";
3 |
4 | export default function Page() {
5 | const ctx = usePageContext();
6 | let { is404, abortReason } = ctx;
7 | if (!abortReason) {
8 | abortReason = is404 ? "Page not found." : "Something went wrong.";
9 | }
10 | return (
11 |
12 | {abortReason}
13 |
14 | );
15 | }
16 |
17 | function Center({ style, ...props }: React.HTMLAttributes) {
18 | return (
19 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/packages/vike-integration/templates/ssr_.template.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 | import { renderPage } from "vike/server";
3 | import { getDefaultEmptyResponseHandler, getDefaultPageContextInit, getDefaultResponseHandler } from "./node-helpers";
4 |
5 | export default async function handler(request: VercelRequest, response: VercelResponse) {
6 | console.debug("query", request.query);
7 | console.debug("url", request.url);
8 | console.debug("headers", request.headers);
9 | const pageContextInit = getDefaultPageContextInit(request);
10 | const pageContext = await renderPage(pageContextInit);
11 | const { httpResponse } = pageContext;
12 |
13 | if (!httpResponse) {
14 | return getDefaultEmptyResponseHandler(response);
15 | }
16 |
17 | return getDefaultResponseHandler(response, httpResponse);
18 | }
19 |
--------------------------------------------------------------------------------
/examples/demo/tests/02-additional-endpoints/fs.test.ts:
--------------------------------------------------------------------------------
1 | import { testFs } from "../common/helpers";
2 | import { describe } from "vitest";
3 | import path from "node:path";
4 |
5 | describe("fs", () => {
6 | testFs(path.basename(__dirname), [
7 | "/config.json",
8 | "/functions/api/name/[name].func/.vc-config.json",
9 | "/functions/api/name/[name].func/index.mjs",
10 | "/functions/api/page.func/index.mjs",
11 | "/functions/api/page.func/.vc-config.json",
12 | "/functions/api/post.func/index.mjs",
13 | "/functions/api/post.func/.vc-config.json",
14 | "/functions/edge.func/index.js",
15 | "/functions/edge.func/.vc-config.json",
16 | "/functions/index2.func/index.mjs",
17 | "/functions/index2.func/.vc-config.json",
18 | "/functions/index3.func/index.mjs",
19 | "/functions/index3.func/.vc-config.json",
20 | "/static/test.html",
21 | "/static/tests/common/index.html",
22 | ]);
23 | });
24 |
--------------------------------------------------------------------------------
/examples/demo/pages/function/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "../../renderer/Link";
3 |
4 | export default function Page(props: { d: string }) {
5 | return (
6 | <>
7 | Welcome
8 | This page is:
9 |
10 | - Using a route function
11 | -
12 | Some pages are static:
13 |
14 | -
15 | /function/a
16 |
17 |
18 |
19 | -
20 | All other pages are dynamic, e.g.:
21 |
22 | -
23 | /function/b
24 |
25 | -
26 | /function/e/f/g/h/i
27 |
28 |
29 |
30 | - {props.d}
31 |
32 | >
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/examples/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-plugin-vercel-simple",
3 | "private": true,
4 | "version": null,
5 | "description": "",
6 | "type": "module",
7 | "scripts": {
8 | "dev": "tsx ./express-entry.ts",
9 | "build": "vite build",
10 | "typecheck": "tsc -p tsconfig.json --noEmit"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "devDependencies": {
15 | "@types/express": "^4.17.25",
16 | "@types/node": "^18.19.130",
17 | "@types/react": "^18.3.27",
18 | "@types/react-dom": "^18.3.7",
19 | "@universal-middleware/express": "^0.2.10",
20 | "@vercel/node": "^3.2.29",
21 | "@vitejs/plugin-react-swc": "^3.11.0",
22 | "express": "^4.22.1",
23 | "react": "^18.3.1",
24 | "tsx": "^4.21.0",
25 | "typescript": "^5.9.3",
26 | "vike": "^0.4.249",
27 | "vike-react": "^0.5.13",
28 | "vite": "^6.4.1"
29 | },
30 | "dependencies": {
31 | "vite-plugin-vercel": "workspace:*"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/vercel/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { normalizePath, type ResolvedConfig, type UserConfig } from "vite";
2 | import path from "node:path";
3 |
4 | export function getRoot(config: UserConfig | ResolvedConfig): string {
5 | return normalizePath(config.root || process.cwd());
6 | }
7 |
8 | export function getOutput(
9 | config: ResolvedConfig,
10 | suffix?: "functions" | `functions/${string}.func` | "static",
11 | ): string {
12 | return path.join(
13 | config.vercel?.outDir ? "" : getRoot(config),
14 | config.vercel?.outDir ?? ".vercel/output",
15 | suffix ?? "",
16 | );
17 | }
18 |
19 | export function getPublic(config: ResolvedConfig): string {
20 | return path.join(getRoot(config), config.publicDir || "public");
21 | }
22 |
23 | export function pathRelativeTo(filePath: string, config: UserConfig | ResolvedConfig, rel: string): string {
24 | const root = getRoot(config);
25 | return normalizePath(path.relative(normalizePath(path.join(root, rel)), filePath));
26 | }
27 |
--------------------------------------------------------------------------------
/examples/demo/tests/common/utils.ts:
--------------------------------------------------------------------------------
1 | import os from "node:os";
2 | import path from "node:path";
3 | import { type InlineConfig, build } from "vite";
4 |
5 | export function getTmpDir(displayName: string) {
6 | return path.join(os.tmpdir(), `vpv-demo-${displayName}`);
7 | }
8 |
9 | export async function callBuild(dirname: string, config: InlineConfig) {
10 | const tmpdir = getTmpDir(dirname);
11 |
12 | await build({
13 | ...config,
14 | vercel: {
15 | ...config.vercel,
16 | additionalEndpoints: [
17 | {
18 | source: "endpoints/edge.ts",
19 | destination: "edge",
20 | route: true,
21 | },
22 | ...(config.vercel?.additionalEndpoints ?? []),
23 | ],
24 | outDir: tmpdir,
25 | },
26 | build: {
27 | ssr: true,
28 | ...config.build,
29 | rollupOptions: {
30 | input: {
31 | "index.html": "tests/common/index.html",
32 | },
33 | },
34 | },
35 | logLevel: "info",
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/examples/demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react-swc";
2 | import vike from "vike/plugin";
3 | import type { UserConfig } from "vite";
4 | import vercel from "vite-plugin-vercel";
5 |
6 | export default {
7 | plugins: [react(), vike(), vercel()],
8 | vercel: {
9 | expiration: 25,
10 | additionalEndpoints: [
11 | {
12 | source: "endpoints/edge.ts",
13 | destination: "edge",
14 | route: true,
15 | },
16 | {
17 | source: "endpoints/og-node.tsx",
18 | destination: "og-node",
19 | route: true,
20 | },
21 | {
22 | source: "endpoints/og-edge.tsx",
23 | destination: "og-edge",
24 | route: true,
25 | },
26 | ],
27 | },
28 | // We manually add a list of dependencies to be pre-bundled, in order to avoid a page reload at dev start which breaks vike's CI
29 | // (The 'react/jsx-runtime' entry is not needed in Vite 3 anymore.)
30 | optimizeDeps: { include: ["cross-fetch", "react/jsx-runtime"] },
31 | } as UserConfig;
32 |
--------------------------------------------------------------------------------
/examples/demo/renderer/+onRenderClient.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderClient
2 | export default onRenderClient;
3 |
4 | import "./css/index.css";
5 | import React from "react";
6 | import ReactDOM from "react-dom/client";
7 | import { PageShell } from "./PageShell";
8 | import { getPageTitle } from "./getPageTitle";
9 | import type { PageContextClient } from "./types";
10 |
11 | let root: ReactDOM.Root;
12 | async function onRenderClient(pageContext: PageContextClient) {
13 | const { Page, pageProps } = pageContext;
14 | const page = (
15 |
16 |
17 |
18 | );
19 | // biome-ignore lint/style/noNonNullAssertion:
20 | const container = document.getElementById("page-view")!;
21 | if (pageContext.isHydration) {
22 | root = ReactDOM.hydrateRoot(container, page);
23 | } else {
24 | if (!root) {
25 | root = ReactDOM.createRoot(container);
26 | }
27 | root.render(page);
28 | }
29 | document.title = getPageTitle(pageContext);
30 | }
31 |
--------------------------------------------------------------------------------
/examples/demo/tests/02-additional-endpoints/globalSetup.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import path from "node:path";
3 | import react from "@vitejs/plugin-react-swc";
4 | import vercel from "vite-plugin-vercel";
5 | import { setup as _setup } from "../common/setup";
6 | import { teardown as _teardown } from "../common/teardown";
7 |
8 | export const setup = _setup(path.basename(__dirname), {
9 | configFile: false,
10 | mode: "production",
11 | root: process.cwd(),
12 | plugins: [
13 | react(),
14 | vercel({
15 | smart: false,
16 | }),
17 | ],
18 | vercel: {
19 | additionalEndpoints: [
20 | {
21 | source: "./tests/common/index2.ts",
22 | destination: "index2",
23 | },
24 | {
25 | source: {
26 | contents: 'console.log("hi");',
27 | sourcefile: "hi.ts",
28 | loader: "ts",
29 | },
30 | destination: "index3",
31 | },
32 | ],
33 | },
34 | });
35 |
36 | export const teardown = _teardown(path.basename(__dirname));
37 |
--------------------------------------------------------------------------------
/examples/demo/tests/05-vike/utils.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import path from "node:path";
3 | import glob from "fast-glob";
4 | import { beforeAll, describe } from "vitest";
5 | import type { TestContext } from "../common/helpers";
6 |
7 | export function prepareTestJsonFileContent(file: string, callback: (context: T) => void) {
8 | const context = {
9 | file: undefined,
10 | } as T;
11 |
12 | beforeAll(async () => {
13 | const dest = path.join(__dirname, "../../.vercel/output", file);
14 | const entries = await glob(dest);
15 |
16 | if (entries.length !== 1) {
17 | throw new Error(`Multiple or no file matches ${dest}`);
18 | }
19 |
20 | const fileContent = await fs.readFile(entries[0], {
21 | encoding: "utf-8",
22 | });
23 |
24 | context.file = JSON.parse(fileContent);
25 | });
26 |
27 | describe(file, () => {
28 | callback(context);
29 | });
30 | }
31 |
32 | export function prepareTestJsonFilesContent(files: string[], callback: (context: T) => void) {
33 | for (const f of files) {
34 | prepareTestJsonFileContent(f, callback);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/vike-integration/route-regex.ts:
--------------------------------------------------------------------------------
1 | export function escapeStringRegexp(str: string) {
2 | return str.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
3 | }
4 |
5 | function getSegmentRegex(segment: string): string {
6 | if (segment.startsWith("@")) {
7 | return "/[^/]+";
8 | }
9 | if (segment === "*") {
10 | return "/.+?";
11 | }
12 | return `/${segment}`;
13 | }
14 |
15 | export function getParametrizedRoute(route: string): string {
16 | const segments = (route.replace(/\/$/, "") || "/").slice(1).split("/");
17 | return segments.map(getSegmentRegex).join("");
18 | }
19 |
20 | export function getRoutesRegex(normalizedRoutes: string[]): string {
21 | const results = normalizedRoutes.map(getParametrizedRoute);
22 | return `^(${results.join("|")})(?:/)?$`;
23 | }
24 |
25 | export function getComplementaryRoutesRegex(normalizedRoutes: string[]): string {
26 | const results = normalizedRoutes.map(getParametrizedRoute);
27 | return results.map((r) => `(?!${r})`).join("");
28 | }
29 |
30 | export function getVercelPattern(route: string): string {
31 | if (route.endsWith("/*")) {
32 | return route.replace(/\/\*/g, "/:any*");
33 | }
34 | return route;
35 | }
36 |
--------------------------------------------------------------------------------
/examples/demo/pages/catch-all/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "../../renderer/Link";
3 |
4 | export default function Page(props: { d: string }) {
5 | return (
6 | <>
7 | Welcome
8 | This page is:
9 |
10 | - Catch-all /catch-all routes
11 | -
12 | Some pages are static:
13 |
14 | -
15 | /catch-all/a/b/c
16 |
17 | -
18 | /catch-all/a/d
19 |
20 |
21 |
22 | -
23 | All other pages are ISR, e.g.:
24 |
25 | -
26 | /catch-all/a
27 |
28 | -
29 | /catch-all/d
30 |
31 | -
32 | /catch-all/e/f/g/h/i
33 |
34 |
35 |
36 | - {props.d}
37 |
38 | >
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/examples/demo/tests/01-minimal/vc-config.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputVcConfigSchema } from "../../../../packages/vercel/src/schemas/config/vc-config";
4 | import { prepareTestJsonFilesContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFilesContent(
7 | path.basename(__dirname),
8 | ["/functions/api/page.func/.vc-config.json", "/functions/api/post.func/.vc-config.json"],
9 | (context) => {
10 | testSchema(context, vercelOutputVcConfigSchema);
11 |
12 | it("should have only necessary properties", () => {
13 | expect(context.file).toStrictEqual({
14 | handler: "index.mjs",
15 | launcherType: "Nodejs",
16 | runtime: "nodejs24.x",
17 | shouldAddHelpers: true,
18 | });
19 | });
20 | },
21 | );
22 |
23 | prepareTestJsonFilesContent(path.basename(__dirname), ["/functions/edge.func/.vc-config.json"], (context) => {
24 | testSchema(context, vercelOutputVcConfigSchema);
25 |
26 | it("should have only necessary properties", () => {
27 | expect(context.file).toStrictEqual({
28 | runtime: "edge",
29 | entrypoint: "index.js",
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/examples/demo/renderer/+onRenderHtml.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderHtml
2 | export default onRenderHtml;
3 |
4 | import ReactDOMServer from "react-dom/server";
5 | import React from "react";
6 | import { escapeInject } from "vike/server";
7 | import { PageShell } from "./PageShell";
8 | import { getPageTitle } from "./getPageTitle";
9 | import type { PageContextServer } from "./types";
10 |
11 | async function onRenderHtml(pageContext: PageContextServer) {
12 | const { Page, pageProps } = pageContext;
13 |
14 | const stream = ReactDOMServer.renderToString(
15 |
16 |
17 | ,
18 | );
19 |
20 | const title = getPageTitle(pageContext);
21 |
22 | const documentHtml = escapeInject`
23 |
24 |
25 | ${title}
26 |
27 |
28 | ${stream}
29 |
30 | `;
31 |
32 | return {
33 | documentHtml,
34 | // See https://vike.dev/stream#initial-data-after-stream-end
35 | pageContext: async () => {
36 | return {
37 | someAsyncProps: 42,
38 | };
39 | },
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/examples/demo/tests/03-prerender/vc-config.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputVcConfigSchema } from "../../../../packages/vercel/src/schemas/config/vc-config";
4 | import { prepareTestJsonFilesContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFilesContent(
7 | path.basename(__dirname),
8 | ["/functions/api/page.func/.vc-config.json", "/functions/api/post.func/.vc-config.json"],
9 | (context) => {
10 | testSchema(context, vercelOutputVcConfigSchema);
11 |
12 | it("should have only necessary properties", () => {
13 | expect(context.file).toStrictEqual({
14 | handler: "index.mjs",
15 | launcherType: "Nodejs",
16 | runtime: "nodejs24.x",
17 | shouldAddHelpers: true,
18 | });
19 | });
20 | },
21 | );
22 |
23 | prepareTestJsonFilesContent(path.basename(__dirname), ["/functions/edge.func/.vc-config.json"], (context) => {
24 | testSchema(context, vercelOutputVcConfigSchema);
25 |
26 | it("should have only necessary properties", () => {
27 | expect(context.file).toStrictEqual({
28 | runtime: "edge",
29 | entrypoint: "index.js",
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [main]
9 | pull_request:
10 | branches: [main]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [20.x, 22.x, 24.x]
19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 | - uses: pnpm/action-setup@v4
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v6
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'pnpm'
29 | - name: Install dependencies
30 | run: pnpm install
31 | - name: Build
32 | run: pnpm run build:all
33 | - name: Typecheck
34 | run: pnpm run typecheck:all
35 | - name: Lint
36 | run: pnpm lint
37 | - name: Test
38 | run: pnpm test
39 |
--------------------------------------------------------------------------------
/examples/demo/renderer/types.ts:
--------------------------------------------------------------------------------
1 | export type { PageContextServer };
2 | export type { PageContextClient };
3 | export type { PageContext };
4 | export type { PageProps };
5 |
6 | import type {
7 | PageContextBuiltInServer,
8 | //*
9 | // When using Client Routing https://vike.dev/clientRouting
10 | PageContextBuiltInClientWithClientRouting as PageContextBuiltInClient,
11 | /*/
12 | // When using Server Routing
13 | PageContextBuiltInClientWithServerRouting as PageContextBuiltInClient
14 | //*/
15 | } from "vike/types";
16 |
17 | type Page = (pageProps: PageProps) => React.ReactElement;
18 | type PageProps = Record;
19 |
20 | export type PageContextCustom = {
21 | Page: Page;
22 | pageProps?: PageProps;
23 | config: {
24 | /** Title defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js) */
25 | title?: string;
26 | };
27 | /** Title defined dynamically by onBeforeRender() */
28 | title?: string;
29 | abortReason?: string;
30 | };
31 |
32 | type PageContextServer = PageContextBuiltInServer & PageContextCustom;
33 | type PageContextClient = PageContextBuiltInClient & PageContextCustom;
34 |
35 | type PageContext = PageContextClient | PageContextServer;
36 |
--------------------------------------------------------------------------------
/examples/demo/tests/04-isr/vc-config.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputVcConfigSchema } from "../../../../packages/vercel/src/schemas/config/vc-config";
4 | import { prepareTestJsonFilesContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFilesContent(
7 | path.basename(__dirname),
8 | [
9 | "/functions/api/page.func/.vc-config.json",
10 | "/functions/api/post.func/.vc-config.json",
11 | "/functions/page1.func/.vc-config.json",
12 | ],
13 | (context) => {
14 | testSchema(context, vercelOutputVcConfigSchema);
15 |
16 | it("should have only necessary properties", () => {
17 | expect(context.file).toStrictEqual({
18 | handler: "index.mjs",
19 | launcherType: "Nodejs",
20 | runtime: "nodejs24.x",
21 | shouldAddHelpers: true,
22 | });
23 | });
24 | },
25 | );
26 |
27 | prepareTestJsonFilesContent(path.basename(__dirname), ["/functions/edge.func/.vc-config.json"], (context) => {
28 | testSchema(context, vercelOutputVcConfigSchema);
29 |
30 | it("should have only necessary properties", () => {
31 | expect(context.file).toStrictEqual({
32 | runtime: "edge",
33 | entrypoint: "index.js",
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/packages/vike-integration/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { existsSync } from "node:fs";
2 | import { rename } from "node:fs/promises";
3 | import { defineConfig } from "tsup";
4 |
5 | export default defineConfig([
6 | {
7 | clean: true,
8 | entry: ["./vike.ts", "./templates/node-helpers.ts", "./templates/edge-helpers.ts", "./+config.ts"],
9 | external: ["esbuild", "rollup", "vite", "vike"],
10 | format: ["esm"],
11 |
12 | platform: "node",
13 | target: "node18",
14 | dts: {
15 | entry: ["./vike.ts", "./templates/node-helpers.ts", "./templates/edge-helpers.ts", "./+config.ts"],
16 | },
17 | async onSuccess() {
18 | // rollup-plugin-dts chooses to rename things its way
19 | // and tsup doesn't wait for dts to be done to trigger this callback...
20 | // so https://github.com/egoist/tsup/issues/700#issuecomment-1499745933
21 |
22 | const start = Date.now();
23 |
24 | // timeout after 5 seconds
25 | while (start + 5000 > Date.now()) {
26 | if (existsSync("./dist/_config.d.ts")) {
27 | break;
28 | }
29 | await new Promise((resolve) => {
30 | setTimeout(resolve, 250);
31 | });
32 | }
33 |
34 | await rename("./dist/_config.d.ts", "./dist/+config.d.ts");
35 | },
36 | },
37 | ]);
38 |
--------------------------------------------------------------------------------
/examples/demo/tests/02-additional-endpoints/vc-config.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputVcConfigSchema } from "../../../../packages/vercel/src/schemas/config/vc-config";
4 | import { prepareTestJsonFilesContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFilesContent(
7 | path.basename(__dirname),
8 | [
9 | "/functions/api/page.func/.vc-config.json",
10 | "/functions/api/post.func/.vc-config.json",
11 | "/functions/index2.func/.vc-config.json",
12 | "/functions/index3.func/.vc-config.json",
13 | ],
14 | (context) => {
15 | testSchema(context, vercelOutputVcConfigSchema);
16 |
17 | it("should have only necessary properties", () => {
18 | expect(context.file).toStrictEqual({
19 | handler: "index.mjs",
20 | launcherType: "Nodejs",
21 | runtime: "nodejs24.x",
22 | shouldAddHelpers: true,
23 | });
24 | });
25 | },
26 | );
27 |
28 | prepareTestJsonFilesContent(path.basename(__dirname), ["/functions/edge.func/.vc-config.json"], (context) => {
29 | testSchema(context, vercelOutputVcConfigSchema);
30 |
31 | it("should have only necessary properties", () => {
32 | expect(context.file).toStrictEqual({
33 | runtime: "edge",
34 | entrypoint: "index.js",
35 | });
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-plugin-vercel-workspace-root",
3 | "packageManager": "pnpm@9.15.9",
4 | "private": "true",
5 | "type": "module",
6 | "scripts": {
7 | "dev:all": "pnpm -r --filter \"./packages/*\" --parallel dev",
8 | "dev:demo": "pnpm -r --filter \"./examples/demo\" --parallel dev",
9 | "vercel-build": "pnpm run build:all",
10 | "build:all": "pnpm -r --filter \"./packages/vercel\" build && pnpm -r --filter \"./packages/vike-integration\" build && pnpm -r --filter \"./examples/*\" build",
11 | "build:demo": "pnpm run build:all && cp -rf examples/demo/public examples/demo/.vercel .",
12 | "typecheck:all": "pnpm -r typecheck",
13 | "test": "pnpm run build:all && pnpm run -r test",
14 | "format": "biome format --write .",
15 | "lint": "biome lint .",
16 | "prepublishOnly": "pnpm run test && changeset version",
17 | "publish:beta": "changeset pre enter beta && pnpm run prepublishOnly && changeset publish -r --tag beta && changeset pre exit",
18 | "publish:stable": "pnpm run prepublishOnly && changeset publish -r"
19 | },
20 | "devDependencies": {
21 | "@biomejs/biome": "^1.9.4",
22 | "@changesets/cli": "^2.29.8",
23 | "@types/node": "^18.19.130",
24 | "cross-env": "^7.0.3",
25 | "typescript": "^5.9.3"
26 | },
27 | "pnpm": {
28 | "overrides": {
29 | "path-to-regexp@>=4.0.0 <6.3.0": "^6.3.0"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/demo/endpoints/og-node.tsx:
--------------------------------------------------------------------------------
1 | import { readFileSync } from "node:fs";
2 | import { join } from "node:path";
3 | import { Readable } from "node:stream";
4 | import type { ReadableStream } from "node:stream/web";
5 | import type { VercelRequest, VercelResponse } from "@vercel/node";
6 | import { ImageResponse } from "@vercel/og";
7 | import React from "react";
8 |
9 | const font = readFileSync(join(__dirname, "./Roboto-Regular.ttf"));
10 |
11 | export default async function handler(request: VercelRequest, response: VercelResponse) {
12 | const resp = new ImageResponse(
13 |
27 | 👋 Hello
28 |
,
29 | {
30 | width: 1200,
31 | height: 630,
32 | fonts: [
33 | {
34 | name: "Roboto",
35 | // Use `fs` (Node.js only) or `fetch` to read the font as Buffer/ArrayBuffer and provide `data` here.
36 | data: font,
37 | weight: 400,
38 | style: "normal",
39 | },
40 | ],
41 | },
42 | );
43 |
44 | Readable.fromWeb(resp.body as ReadableStream).pipe(response);
45 | }
46 |
--------------------------------------------------------------------------------
/examples/demo/tests/05-vike/vc-config.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, it } from "vitest";
2 | import { vercelOutputVcConfigSchema } from "../../../../packages/vercel/src/schemas/config/vc-config";
3 | import { testSchema } from "../common/helpers";
4 | import { prepareTestJsonFilesContent } from "./utils";
5 |
6 | prepareTestJsonFilesContent(
7 | [
8 | "/functions/api/page.func/.vc-config.json",
9 | "/functions/api/post.func/.vc-config.json",
10 | "/functions/ssr_.func/.vc-config.json",
11 | "/functions/pages/catch-all-*.func/.vc-config.json",
12 | "/functions/pages/isr-*.func/.vc-config.json",
13 | "/functions/pages/named-*.func/.vc-config.json",
14 | ],
15 | (context) => {
16 | testSchema(context, vercelOutputVcConfigSchema);
17 |
18 | it("should have only necessary properties", () => {
19 | expect(context.file).toStrictEqual({
20 | handler: "index.mjs",
21 | launcherType: "Nodejs",
22 | runtime: "nodejs24.x",
23 | shouldAddHelpers: true,
24 | supportsResponseStreaming: true,
25 | });
26 | });
27 | },
28 | );
29 |
30 | prepareTestJsonFilesContent(["/functions/edge.func/.vc-config.json"], (context) => {
31 | testSchema(context, vercelOutputVcConfigSchema);
32 |
33 | it("should have only necessary properties", () => {
34 | expect(context.file).toStrictEqual({
35 | runtime: "edge",
36 | entrypoint: "index.js",
37 | });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/examples/demo/renderer/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 | import config from "@vite-plugin-vercel/vike/config";
3 |
4 | // https://vike.dev/config
5 | export default {
6 | passToClient: ["pageProps", "title", "someAsyncProps"],
7 | clientRouting: true,
8 | hydrationCanBeAborted: true,
9 | // https://vike.dev/meta
10 | meta: {
11 | // Create new config 'title'
12 | title: {
13 | env: { server: true, client: true },
14 | },
15 | // Create new config 'onBeforeRenderIsomorph'
16 | onBeforeRenderIsomorph: {
17 | env: { config: true },
18 | effect({ configDefinedAt, configValue }) {
19 | if (typeof configValue !== "boolean") {
20 | throw new Error(`${configDefinedAt} should be a boolean`);
21 | }
22 | if (configValue) {
23 | return {
24 | meta: {
25 | onBeforeRender: {
26 | // We override VPS's default behavior of always loading/executing onBeforeRender() on the server-side.
27 | // If we set onBeforeRenderIsomorph to true, then onBeforeRender() is loaded/executed in the browser as well, allowing us to fetch data direcly from the browser upon client-side navigation (without involving our Node.js/Edge server at all).
28 | env: { server: true, client: true },
29 | },
30 | },
31 | };
32 | }
33 | },
34 | },
35 | },
36 | prerender: true,
37 | extends: config,
38 | } satisfies Config;
39 |
--------------------------------------------------------------------------------
/examples/demo/pages/named/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "../../renderer/Link";
3 |
4 | function isISR(someId: string) {
5 | return someId !== "id-1" && someId !== "id-2";
6 | }
7 |
8 | export default function Page(props: { d: string; someId: string }) {
9 | return (
10 | <>
11 | Welcome
12 | This page is:
13 |
14 | - Static with url parameter: {props.someId}
15 | -
16 | Some pages are static:
17 |
18 | -
19 | /named/id-1
20 |
21 | -
22 | /named/id-2
23 |
24 |
25 |
26 | -
27 | All other pages are ISR, e.g.:
28 |
29 | -
30 | /named/id-3
31 |
32 | -
33 | /named/something
34 |
35 |
36 |
37 | -
38 | 404 on sub-pages, e.g.:
39 |
40 | -
41 | /named/id-1/a
42 |
43 | -
44 | /named/something/a
45 |
46 |
47 |
48 | - {isISR(props.someId) ? "ISR" : "Static"}
49 | - {props.d}
50 |
51 | >
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/packages/vercel/src/schemas/config/vc-config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Schema definition for `.vercel/output/functions/.func/.vc-config.json`
3 | * @see {@link https://vercel.com/docs/build-output-api/v3/primitives#serverless-function-configuration}
4 | */
5 |
6 | import { z } from "zod";
7 |
8 | export const vercelOutputEdgeVcConfigSchema = z
9 | .object({
10 | runtime: z.literal("edge"),
11 | entrypoint: z.string(),
12 | envVarsInUse: z.array(z.string()).optional(),
13 | })
14 | .strict();
15 |
16 | export const vercelOutputServerlessVcConfigSchema = z
17 | .object({
18 | runtime: z.string(),
19 | handler: z.string(),
20 | memory: z.number().int().min(128).max(3008).optional(),
21 | maxDuration: z.number().int().positive().optional(),
22 | environment: z.record(z.string()).optional(),
23 | regions: z.array(z.string()).optional(),
24 | supportsWrapper: z.boolean().optional(),
25 | supportsResponseStreaming: z.boolean().optional(),
26 | })
27 | .strict();
28 |
29 | export const vercelOutputServerlessNodeVcConfigSchema = vercelOutputServerlessVcConfigSchema
30 | .extend({
31 | launcherType: z.literal("Nodejs"),
32 | shouldAddHelpers: z.boolean().optional(),
33 | shouldAddSourcemapSupport: z.boolean().optional(),
34 | awsLambdaHandler: z.string().optional(),
35 | })
36 | .strict();
37 |
38 | export const vercelOutputVcConfigSchema = z.union([
39 | vercelOutputEdgeVcConfigSchema,
40 | vercelOutputServerlessVcConfigSchema,
41 | vercelOutputServerlessNodeVcConfigSchema,
42 | ]);
43 |
44 | export type VercelOutputVcConfig = z.infer;
45 |
--------------------------------------------------------------------------------
/packages/vike-integration/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vite-plugin-vercel/vike",
3 | "version": "9.1.0",
4 | "type": "module",
5 | "files": [
6 | "dist",
7 | "templates",
8 | "*.d.ts",
9 | "helpers.js",
10 | "config.js"
11 | ],
12 | "description": "Use vike with vite-plugin-vercel",
13 | "author": "Joël Charles ",
14 | "repository": "https://github.com/magne4000/vite-plugin-vercel",
15 | "main": "./dist/vike.js",
16 | "types": "./dist/vike.d.ts",
17 | "exports": {
18 | ".": "./dist/vike.js",
19 | "./helpers/node": "./dist/templates/node-helpers.js",
20 | "./helpers/edge": "./dist/templates/edge-helpers.js",
21 | "./config": {
22 | "types": "./config.d.ts",
23 | "default": "./dist/+config.js"
24 | }
25 | },
26 | "scripts": {
27 | "build": "tsup",
28 | "dev": "tsup --watch",
29 | "prepack": "rm -rf dist && pnpm build",
30 | "typecheck": "tsc -p tsconfig.json --noEmit",
31 | "lint:ts": "eslint . --max-warnings 0 --ignore-pattern dist"
32 | },
33 | "devDependencies": {
34 | "@types/node": "^18.19.130",
35 | "@types/qs": "^6.14.0",
36 | "@vercel/edge": "^1.2.2",
37 | "@vercel/node": "^3.2.29",
38 | "tsup": "^8.5.1",
39 | "typescript": "^5.9.3",
40 | "vike": "^0.4.249",
41 | "vite": "^6.4.1",
42 | "vite-plugin-vercel": "workspace:*"
43 | },
44 | "dependencies": {
45 | "@brillout/libassert": "^0.5.8",
46 | "nanoid": "^5.1.6",
47 | "qs": "^6.14.0"
48 | },
49 | "peerDependencies": {
50 | "vike": "^0.4.229",
51 | "vite": "^5.0.2 || ^6",
52 | "vite-plugin-vercel": "*"
53 | },
54 | "license": "MIT"
55 | }
56 |
--------------------------------------------------------------------------------
/examples/demo/tests/01-minimal/config.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
4 | import { prepareTestJsonFileContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFileContent(path.basename(__dirname), "config.json", (context) => {
7 | testSchema(context, vercelOutputConfigSchema);
8 |
9 | it("should have defaults routes only", () => {
10 | expect(context.file).toHaveProperty("routes", [
11 | {
12 | src: "^/api/page$",
13 | headers: { "X-VitePluginVercel-Test": "test" },
14 | continue: true,
15 | },
16 | {
17 | headers: { Location: "/$1" },
18 | src: "^/(?:(.+)/)?index(?:\\.html)?/?$",
19 | status: 308,
20 | },
21 | {
22 | headers: { Location: "/$1" },
23 | src: "^/(.*)\\.html/?$",
24 | status: 308,
25 | },
26 | { handle: "filesystem" },
27 | {
28 | src: "^/edge(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
29 | dest: "/edge/$1",
30 | check: true,
31 | },
32 | {
33 | check: true,
34 | src: "^/api/page$",
35 | dest: "/api/page",
36 | },
37 | {
38 | check: true,
39 | src: "^/api/post$",
40 | dest: "/api/post",
41 | },
42 | {
43 | check: true,
44 | src: "^/api/name(?:/([^/]+?))$",
45 | dest: "/api/name/[name]?name=$1",
46 | },
47 | ]);
48 | expect(context.file).toHaveProperty("overrides", {});
49 | expect(Object.keys(context.file as any).sort()).toMatchObject(["version", "overrides", "routes"].sort());
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/examples/demo/tests/03-prerender/config.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
4 | import { prepareTestJsonFileContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFileContent(path.basename(__dirname), "config.json", (context) => {
7 | testSchema(context, vercelOutputConfigSchema);
8 |
9 | it("should have defaults routes only", () => {
10 | expect(context.file).toHaveProperty("routes", [
11 | {
12 | src: "^/api/page$",
13 | headers: { "X-VitePluginVercel-Test": "test" },
14 | continue: true,
15 | },
16 | {
17 | headers: { Location: "/$1" },
18 | src: "^/(?:(.+)/)?index(?:\\.html)?/?$",
19 | status: 308,
20 | },
21 | {
22 | headers: { Location: "/$1" },
23 | src: "^/(.*)\\.html/?$",
24 | status: 308,
25 | },
26 | { handle: "filesystem" },
27 | {
28 | src: "^/edge(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
29 | dest: "/edge/$1",
30 | check: true,
31 | },
32 | {
33 | check: true,
34 | src: "^/api/page$",
35 | dest: "/api/page",
36 | },
37 | {
38 | check: true,
39 | src: "^/api/post$",
40 | dest: "/api/post",
41 | },
42 | {
43 | check: true,
44 | src: "^/api/name(?:/([^/]+?))$",
45 | dest: "/api/name/[name]?name=$1",
46 | },
47 | ]);
48 | expect(context.file).toHaveProperty("overrides", {
49 | ssr: { path: "ssr_" },
50 | });
51 | expect(Object.keys(context.file as any).sort()).toEqual(["version", "overrides", "routes"].sort());
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/examples/demo/tests/05-vike/prerender.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, it } from "vitest";
2 | import { vercelOutputPrerenderConfigSchema } from "../../../../packages/vercel/src/schemas/config/prerender-config";
3 | import { testSchema } from "../common/helpers";
4 | import { prepareTestJsonFileContent } from "./utils";
5 |
6 | prepareTestJsonFileContent("/functions/pages/catch-all-*.prerender-config.json", (context) => {
7 | testSchema(context, vercelOutputPrerenderConfigSchema);
8 |
9 | it("should have only necessary properties", () => {
10 | expect(Object.keys(context.file as any)).toHaveLength(2);
11 | expect(context.file).toHaveProperty("expiration", 15);
12 | expect(context.file).toHaveProperty("group");
13 | expect((context.file as any).group).toBeTypeOf("number");
14 | });
15 | });
16 |
17 | prepareTestJsonFileContent("/functions/pages/isr-*.prerender-config.json", (context) => {
18 | testSchema(context, vercelOutputPrerenderConfigSchema);
19 |
20 | it("should have only necessary properties", () => {
21 | expect(Object.keys(context.file as any)).toHaveLength(2);
22 | expect(context.file).toHaveProperty("expiration", 15);
23 | expect(context.file).toHaveProperty("group");
24 | expect((context.file as any).group).toBeTypeOf("number");
25 | });
26 | });
27 |
28 | prepareTestJsonFileContent("/functions/pages/named-*.prerender-config.json", (context) => {
29 | testSchema(context, vercelOutputPrerenderConfigSchema);
30 |
31 | it("should have only necessary properties", () => {
32 | expect(Object.keys(context.file as any)).toHaveLength(2);
33 | expect(context.file).toHaveProperty("expiration", 25);
34 | expect(context.file).toHaveProperty("group");
35 | expect((context.file as any).group).toBeTypeOf("number");
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/examples/demo/tests/04-isr/config.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { expect, it } from "vitest";
3 | import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
4 | import { prepareTestJsonFileContent, testSchema } from "../common/helpers";
5 |
6 | prepareTestJsonFileContent(path.basename(__dirname), "config.json", (context) => {
7 | testSchema(context, vercelOutputConfigSchema);
8 |
9 | it("should have defaults routes only", () => {
10 | expect(context.file).toHaveProperty("routes", [
11 | {
12 | src: "^/api/page$",
13 | headers: { "X-VitePluginVercel-Test": "test" },
14 | continue: true,
15 | },
16 | {
17 | headers: { Location: "/$1" },
18 | src: "^/(?:(.+)/)?index(?:\\.html)?/?$",
19 | status: 308,
20 | },
21 | {
22 | headers: { Location: "/$1" },
23 | src: "^/(.*)\\.html/?$",
24 | status: 308,
25 | },
26 | { handle: "filesystem" },
27 | {
28 | src: "^/edge(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
29 | dest: "/edge/$1",
30 | check: true,
31 | },
32 | {
33 | check: true,
34 | src: "^/api/page$",
35 | dest: "/api/page",
36 | },
37 | {
38 | check: true,
39 | src: "^/api/post$",
40 | dest: "/api/post",
41 | },
42 | {
43 | check: true,
44 | src: "^/api/name(?:/([^/]+?))$",
45 | dest: "/api/name/[name]?name=$1",
46 | },
47 | { check: true, dest: "page1/?__original_path=$1", src: "^(/page1)$" },
48 | ]);
49 | expect(context.file).toHaveProperty("overrides", {});
50 | expect(Object.keys(context.file as any).sort()).toEqual(["version", "overrides", "routes"].sort());
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/packages/vike-integration/templates/node-helpers.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from "@vercel/node";
2 | import type { renderPage } from "vike";
3 | import { getOriginalUrl } from "./utils";
4 |
5 | type HttpResponse = NonNullable>["httpResponse"]>;
6 |
7 | /**
8 | * Extract useful pageContext from request to give to renderPage(...).
9 | * It handles internals such as retrieving the original url from `x-now-route-matches` header or from `__original_path` query,
10 | * so it's highly recommended to use it instead of `request.url`.
11 | * @param request
12 | */
13 | export function getDefaultPageContextInit(request: VercelRequest) {
14 | const query: Record = request.query ?? {};
15 | const url = getOriginalUrl(request.headers["x-now-route-matches"], query.__original_path, request.url);
16 | return {
17 | urlOriginal: url,
18 | headersOriginal: request.headers,
19 | body: request.body,
20 | cookies: request.cookies,
21 | };
22 | }
23 |
24 | /**
25 | * Send a default empty HTML response
26 | * @param response
27 | */
28 | export function getDefaultEmptyResponseHandler(response: VercelResponse) {
29 | response.statusCode = 200;
30 | response.setHeader("content-type", "text/html; charset=UTF-8");
31 | return response.end("");
32 | }
33 |
34 | /**
35 | * Send `httpResponse` through `response`
36 | * @param response
37 | * @param httpResponse
38 | */
39 | export function getDefaultResponseHandler(response: VercelResponse, httpResponse: HttpResponse) {
40 | const { statusCode, headers } = httpResponse;
41 |
42 | response.statusCode = statusCode;
43 | for (const [name, value] of headers) {
44 | response.setHeader(name, value);
45 | }
46 |
47 | return httpResponse.pipe(response);
48 | }
49 |
--------------------------------------------------------------------------------
/packages/vercel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-plugin-vercel",
3 | "version": "9.1.1",
4 | "type": "module",
5 | "files": [
6 | "dist",
7 | "*.d.ts"
8 | ],
9 | "main": "./dist/index.cjs",
10 | "module": "./dist/index.js",
11 | "exports": {
12 | ".": {
13 | "types": "./index.d.ts",
14 | "import": "./dist/index.js",
15 | "require": "./dist/index.cjs"
16 | },
17 | "./types": {
18 | "types": "./index.d.ts"
19 | }
20 | },
21 | "types": "./index.d.ts",
22 | "description": "Vercel adapter for vite",
23 | "author": "Joël Charles ",
24 | "repository": "https://github.com/magne4000/vite-plugin-vercel",
25 | "license": "MIT",
26 | "scripts": {
27 | "build": "tsup",
28 | "dev": "tsup --watch",
29 | "prepack": "rm -rf dist && pnpm build",
30 | "typecheck": "tsc -p tsconfig.json --noEmit"
31 | },
32 | "peerDependencies": {
33 | "@vite-plugin-vercel/vike": "workspace:*",
34 | "vike": "*",
35 | "vite": "^4.4 || ^5.0.2 || ^6"
36 | },
37 | "peerDependenciesMeta": {
38 | "@vite-plugin-vercel/vike": {
39 | "optional": true
40 | },
41 | "vike": {
42 | "optional": true
43 | }
44 | },
45 | "devDependencies": {
46 | "@types/node": "^18.19.130",
47 | "@vite-plugin-vercel/vike": "workspace:*",
48 | "tsup": "^8.5.1",
49 | "typescript": "^5.9.3",
50 | "vike": "^0.4.249",
51 | "vite": "^6.4.1"
52 | },
53 | "dependencies": {
54 | "@brillout/libassert": "^0.5.8",
55 | "@manypkg/find-root": "^2.2.3",
56 | "@vercel/build-utils": "^13.2.3",
57 | "@vercel/nft": "^1.1.1",
58 | "@vercel/routing-utils": "^5.3.1",
59 | "esbuild": "^0.27.1",
60 | "fast-glob": "^3.3.3",
61 | "magicast": "^0.5.1",
62 | "zod": "^3.25.76"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/examples/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-plugin-vercel-demo-v1",
3 | "private": "true",
4 | "scripts": {
5 | "dev": "vike dev",
6 | "build": "vike build",
7 | "preview": "vike build && vike preview",
8 | "typecheck": "tsc -p tsconfig.json --noEmit",
9 | "test": "pnpm run test:01 && pnpm run test:02 && pnpm run test:03 && pnpm run test:04 && pnpm run test:05",
10 | "test:01": "vitest run --dir tests/01-minimal --config tests/01-minimal/vitest.config.ts",
11 | "test:02": "vitest run --dir tests/02-additional-endpoints --config tests/02-additional-endpoints/vitest.config.ts",
12 | "test:03": "vitest run --dir tests/03-prerender --config tests/03-prerender/vitest.config.ts",
13 | "test:04": "vitest run --dir tests/04-isr --config tests/04-isr/vitest.config.ts",
14 | "test:05": "vite build && vitest run --dir tests/05-vike --config tests/05-vike/vitest.config.ts"
15 | },
16 | "dependencies": {
17 | "@mdx-js/mdx": "^3.1.1",
18 | "@mdx-js/react": "^3.1.1",
19 | "@mdx-js/rollup": "^3.1.1",
20 | "@vercel/edge-config": "^1.4.3",
21 | "@vercel/og": "^0.6.8",
22 | "@vite-plugin-vercel/vike": "workspace:*",
23 | "@vitejs/plugin-react-swc": "^3.11.0",
24 | "cross-fetch": "^4.1.0",
25 | "node-fetch": "^3.3.2",
26 | "react": "^18.3.1",
27 | "react-dom": "^18.3.1",
28 | "typescript": "^5.9.3",
29 | "vike": "^0.4.249",
30 | "vite": "^6.4.1",
31 | "vite-plugin-vercel": "workspace:*"
32 | },
33 | "type": "module",
34 | "version": null,
35 | "devDependencies": {
36 | "@types/node": "^18.19.130",
37 | "@types/node-fetch": "^2.6.13",
38 | "@types/react": "^18.3.27",
39 | "@types/react-dom": "^18.3.7",
40 | "@vercel/node": "^3.2.29",
41 | "fast-glob": "^3.3.3",
42 | "vitest": "^3.2.4",
43 | "zod": "^3.25.76"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/express/express-entry.ts:
--------------------------------------------------------------------------------
1 | import { dirname } from "node:path";
2 | import { fileURLToPath } from "node:url";
3 |
4 | import { createHandler, createMiddleware } from "@universal-middleware/express";
5 | import express from "express";
6 | import { vikeHandler } from "./server/vike-handler";
7 |
8 | const __filename = globalThis.__filename ?? fileURLToPath(import.meta.url);
9 | const __dirname = globalThis.__dirname ?? dirname(__filename);
10 | const root = __dirname;
11 | const port = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3000;
12 | const hmrPort = process.env.HMR_PORT ? Number.parseInt(process.env.HMR_PORT, 10) : 24678;
13 |
14 | export default await startServer();
15 |
16 | async function startServer() {
17 | const app = express();
18 |
19 | if (process.env.NODE_ENV === "production") {
20 | app.use(express.static(`${root}/dist/client`));
21 | } else {
22 | // Instantiate Vite's development server and integrate its middleware to our server.
23 | // ! We should instantiate it *only* in development. (It isn't needed in production
24 | // and would unnecessarily bloat our server in production.)
25 | const vite = await import("vite");
26 | const viteDevMiddleware = (
27 | await vite.createServer({
28 | root,
29 | server: { middlewareMode: true, hmr: { port: hmrPort } },
30 | })
31 | ).middlewares;
32 | app.use(viteDevMiddleware);
33 | }
34 |
35 | app.get(
36 | "/hello",
37 | createMiddleware(() => () => {
38 | console.log("HELLO");
39 | return new Response("Hello");
40 | })(),
41 | );
42 |
43 | /**
44 | * Vike route
45 | *
46 | * @link {@see https://vike.dev}
47 | **/
48 | app.all("*", createHandler(() => vikeHandler)());
49 |
50 | app.listen(port, () => {
51 | console.log(`Server listening on http://localhost:${port}`);
52 | });
53 |
54 | return app;
55 | }
56 |
--------------------------------------------------------------------------------
/packages/vike-integration/templates/edge-helpers.ts:
--------------------------------------------------------------------------------
1 | import { parse } from "qs";
2 | import type { renderPage } from "vike";
3 | import { getOriginalUrl } from "./utils";
4 |
5 | type HttpResponse = NonNullable>["httpResponse"]>;
6 |
7 | /**
8 | * Extract useful pageContext from request to give to renderPage(...).
9 | * It handles internals such as retrieving the original url from `x-now-route-matches` header or from `__original_path` query,
10 | * so it's highly recommended to use it instead of `request.url`.
11 | * @param request
12 | */
13 | export function getDefaultPageContextInit(request: Request) {
14 | const queryString = new URL(request.url).search.slice(1); // Remove the leading "?" with .slice(1)
15 | const query = parse(queryString);
16 | const url = getOriginalUrl(request.headers.get("x-now-route-matches"), query.__original_path, request.url);
17 | const cookieHeader = request.headers.get("Cookie") || "";
18 |
19 | const cookies = cookieHeader.split("; ").reduce(
20 | (acc, cookie) => {
21 | const [name, value] = cookie.split("=");
22 | acc[name] = value;
23 | return acc;
24 | },
25 | {} as Record,
26 | );
27 |
28 | return {
29 | urlOriginal: url,
30 | headersOriginal: request.headers as Headers,
31 | body: request.body,
32 | cookies,
33 | };
34 | }
35 |
36 | /**
37 | * Send a default empty HTML response
38 | */
39 | export function getDefaultEmptyResponseHandler(): Response {
40 | return new Response(null, {
41 | status: 200,
42 | headers: {
43 | "content-type": "text/html; charset=UTF-8",
44 | },
45 | });
46 | }
47 |
48 | /**
49 | * Send `httpResponse` through `response`
50 | */
51 | export function getDefaultResponseHandler(httpResponse: HttpResponse): Response {
52 | const { statusCode, headers } = httpResponse;
53 |
54 | return new Response(httpResponse.getReadableWebStream(), {
55 | status: statusCode,
56 | headers,
57 | });
58 | }
59 |
--------------------------------------------------------------------------------
/examples/demo/tests/02-additional-endpoints/config.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, it } from "vitest";
2 | import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
3 | import { prepareTestJsonFileContent, testSchema } from "../common/helpers";
4 |
5 | import path from "node:path";
6 |
7 | prepareTestJsonFileContent(path.basename(__dirname), "config.json", (context) => {
8 | testSchema(context, vercelOutputConfigSchema);
9 |
10 | it("should have defaults routes only", () => {
11 | expect(context.file).toHaveProperty("routes", [
12 | {
13 | src: "^/api/page$",
14 | headers: { "X-VitePluginVercel-Test": "test" },
15 | continue: true,
16 | },
17 | {
18 | headers: { Location: "/$1" },
19 | src: "^/(?:(.+)/)?index(?:\\.html)?/?$",
20 | status: 308,
21 | },
22 | {
23 | headers: { Location: "/$1" },
24 | src: "^/(.*)\\.html/?$",
25 | status: 308,
26 | },
27 | { handle: "filesystem" },
28 | {
29 | src: "^/edge(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
30 | dest: "/edge/$1",
31 | check: true,
32 | },
33 | {
34 | check: true,
35 | src: "^/index2(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
36 | dest: "/index2/$1",
37 | },
38 | {
39 | check: true,
40 | src: "^/index3(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
41 | dest: "/index3/$1",
42 | },
43 | {
44 | check: true,
45 | src: "^/api/page$",
46 | dest: "/api/page",
47 | },
48 | {
49 | check: true,
50 | src: "^/api/post$",
51 | dest: "/api/post",
52 | },
53 | {
54 | check: true,
55 | src: "^/api/name(?:/([^/]+?))$",
56 | dest: "/api/name/[name]?name=$1",
57 | },
58 | ]);
59 | expect(context.file).toHaveProperty("overrides", {});
60 | expect(Object.keys(context.file as any).sort()).toEqual(["version", "overrides", "routes"].sort());
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/examples/demo/tests/common/helpers.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import path from "node:path";
3 | import glob from "fast-glob";
4 | import { beforeAll, describe, expect, it } from "vitest";
5 | import type { ZodSchema } from "zod";
6 | import { getTmpDir } from "./utils";
7 |
8 | export interface TestContext {
9 | file: unknown;
10 | }
11 |
12 | export function testFs(dirname: string, filesOrCallback: Iterable | ((entries: string[]) => void)) {
13 | it("should generate the right files", async () => {
14 | const tmpdir = getTmpDir(dirname);
15 | const entries = await glob(`${tmpdir}/**`, { dot: true });
16 | let mappedEntries = entries.map((e) => e.replace(tmpdir, "")).filter((e) => !e.startsWith("/_ignore"));
17 |
18 | mappedEntries = Array.from(new Set(mappedEntries));
19 |
20 | if (typeof filesOrCallback === "function") {
21 | filesOrCallback(mappedEntries);
22 | } else {
23 | expect(mappedEntries.sort()).toMatchObject(Array.from(filesOrCallback).sort());
24 | }
25 | });
26 | }
27 |
28 | export function testSchema(context: TestContext, schema: ZodSchema) {
29 | it("should respect schema", () => {
30 | expect(schema.safeParse(context.file)).not.toHaveProperty("error");
31 | });
32 | }
33 |
34 | export function prepareTestJsonFilesContent(
35 | dirname: string,
36 | files: string[],
37 | callback: (context: T) => void,
38 | ) {
39 | for (const f of files) {
40 | prepareTestJsonFileContent(dirname, f, callback);
41 | }
42 | }
43 |
44 | export function prepareTestJsonFileContent(
45 | dirname: string,
46 | file: string,
47 | callback: (context: T) => void,
48 | ) {
49 | const context = {
50 | file: undefined,
51 | } as T;
52 |
53 | beforeAll(async () => {
54 | const dest = path.join(getTmpDir(dirname), file);
55 | const entries = await glob(dest);
56 |
57 | if (entries.length !== 1) {
58 | throw new Error(`Multiple or no file matches ${dest}`);
59 | }
60 |
61 | const fileContent = await fs.readFile(entries[0], {
62 | encoding: "utf-8",
63 | });
64 |
65 | context.file = JSON.parse(fileContent);
66 | });
67 |
68 | describe(file, () => {
69 | callback(context);
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/examples/demo/renderer/PageShell.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "./Link";
3 | import logoUrl from "./logo.svg";
4 | import type { PageContext } from "./types";
5 | import { PageContextProvider } from "./usePageContext";
6 |
7 | export { PageShell };
8 |
9 | function PageShell({
10 | pageContext,
11 | children,
12 | }: {
13 | pageContext: PageContext;
14 | children: React.ReactNode;
15 | }) {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 | Home
23 | Dynamic
24 | Static
25 | ISR
26 | Named
27 | Catch-all
28 | Function
29 | Edge Function endpoint
30 | Vike Edge Function endpoint
31 | Edge OG endpoint
32 | Node OG endpoint
33 |
34 | {children}
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | function Layout({ children }: { children: React.ReactNode }) {
42 | return (
43 |
50 | {children}
51 |
52 | );
53 | }
54 |
55 | function Sidebar({ children }: { children: React.ReactNode }) {
56 | return (
57 |
70 | );
71 | }
72 |
73 | function Content({ children }: { children: React.ReactNode }) {
74 | return (
75 |
76 |
84 | {children}
85 |
86 |
87 | );
88 | }
89 |
90 | function Logo() {
91 | return (
92 |
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Vercel
95 | .output
96 | .vercel
97 |
98 | # Gatsby files
99 | .cache/
100 | # Comment in the public line in if your project uses Gatsby and not Next.js
101 | # https://nextjs.org/blog/next-9-1#public-directory-support
102 | # public
103 |
104 | # vuepress build output
105 | .vuepress/dist
106 |
107 | # vuepress v2.x temp and cache directory
108 | .temp
109 | .cache
110 |
111 | # Serverless directories
112 | .serverless/
113 |
114 | # FuseBox cache
115 | .fusebox/
116 |
117 | # DynamoDB Local files
118 | .dynamodb/
119 |
120 | # TernJS port file
121 | .tern-port
122 |
123 | # Stores VSCode versions used for testing VSCode extensions
124 | .vscode-test
125 |
126 | # yarn v2
127 | .yarn/cache
128 | .yarn/unplugged
129 | .yarn/build-state.yml
130 | .yarn/install-state.gz
131 | .pnp.*
132 |
133 | # Local files
134 | *.local.*
135 |
136 | # can generated by Vite in case of error
137 | vite.config.ts.js
138 |
139 | .idea
140 |
141 |
--------------------------------------------------------------------------------
/examples/express/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # vite-plugin-vercel-simple
2 |
3 | ## null
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies
8 | - vite-plugin-vercel@6.0.1
9 |
10 | ## null
11 |
12 | ### Patch Changes
13 |
14 | - Updated dependencies
15 | - vite-plugin-vercel@6.0.0
16 |
17 | ## null
18 |
19 | ### Patch Changes
20 |
21 | - Updated dependencies
22 | - vite-plugin-vercel@5.0.5
23 |
24 | ## null
25 |
26 | ### Patch Changes
27 |
28 | - Updated dependencies
29 | - vite-plugin-vercel@5.0.4
30 |
31 | ## null
32 |
33 | ### Patch Changes
34 |
35 | - Updated dependencies
36 | - vite-plugin-vercel@5.0.3
37 |
38 | ## null
39 |
40 | ### Patch Changes
41 |
42 | - vite-plugin-vercel@5.0.2
43 |
44 | ## null
45 |
46 | ### Patch Changes
47 |
48 | - vite-plugin-vercel@5.0.1
49 |
50 | ## null
51 |
52 | ### Patch Changes
53 |
54 | - Updated dependencies
55 | - vite-plugin-vercel@5.0.0
56 |
57 | ## null
58 |
59 | ### Patch Changes
60 |
61 | - Updated dependencies
62 | - vite-plugin-vercel@4.0.2
63 |
64 | ## null
65 |
66 | ### Patch Changes
67 |
68 | - Updated dependencies
69 | - vite-plugin-vercel@4.0.1
70 |
71 | ## null
72 |
73 | ### Patch Changes
74 |
75 | - Updated dependencies
76 | - vite-plugin-vercel@4.0.0
77 |
78 | ## null
79 |
80 | ### Patch Changes
81 |
82 | - Updated dependencies
83 | - vite-plugin-vercel@3.0.2
84 |
85 | ## null
86 |
87 | ### Patch Changes
88 |
89 | - Updated dependencies [8e7cad4]
90 | - vite-plugin-vercel@3.0.1
91 |
92 | ## null
93 |
94 | ### Patch Changes
95 |
96 | - Updated dependencies [ec8dcba]
97 | - vite-plugin-vercel@3.0.0
98 |
99 | ## null
100 |
101 | ### Patch Changes
102 |
103 | - Updated dependencies
104 | - vite-plugin-vercel@2.0.1
105 |
106 | ## null
107 |
108 | ### Patch Changes
109 |
110 | - Updated dependencies
111 | - vite-plugin-vercel@2.0.0
112 |
113 | ## null
114 |
115 | ### Patch Changes
116 |
117 | - Updated dependencies
118 | - vite-plugin-vercel@1.0.0
119 |
120 | ## null
121 |
122 | ### Patch Changes
123 |
124 | - Updated dependencies
125 | - vite-plugin-vercel@0.3.7
126 |
127 | ## null
128 |
129 | ### Patch Changes
130 |
131 | - Updated dependencies
132 | - vite-plugin-vercel@0.3.6
133 |
134 | ## null
135 |
136 | ### Patch Changes
137 |
138 | - Updated dependencies
139 | - vite-plugin-vercel@0.3.5
140 |
141 | ## null
142 |
143 | ### Patch Changes
144 |
145 | - Updated dependencies
146 | - vite-plugin-vercel@1.0.0
147 |
148 | ## null
149 |
150 | ### Patch Changes
151 |
152 | - Updated dependencies
153 | - vite-plugin-vercel@0.3.3
154 |
155 | ## null
156 |
157 | ### Patch Changes
158 |
159 | - Updated dependencies
160 | - vite-plugin-vercel@0.3.2
161 |
162 | ## null
163 |
164 | ### Patch Changes
165 |
166 | - Updated dependencies
167 | - vite-plugin-vercel@0.3.1
168 |
169 | ## null
170 |
171 | ### Patch Changes
172 |
173 | - Updated dependencies
174 | - vite-plugin-vercel@0.3.0
175 |
176 | ## null
177 |
178 | ### Patch Changes
179 |
180 | - Updated dependencies
181 | - vite-plugin-vercel@0.2.2
182 |
183 | ## null
184 |
185 | ### Patch Changes
186 |
187 | - Updated dependencies
188 | - vite-plugin-vercel@0.2.1
189 |
--------------------------------------------------------------------------------
/examples/simple/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # vite-plugin-vercel-simple
2 |
3 | ## null
4 |
5 | ### Patch Changes
6 |
7 | - vite-plugin-vercel@7.0.0
8 |
9 | ## null
10 |
11 | ### Patch Changes
12 |
13 | - Updated dependencies
14 | - vite-plugin-vercel@6.0.1
15 |
16 | ## null
17 |
18 | ### Patch Changes
19 |
20 | - Updated dependencies
21 | - vite-plugin-vercel@6.0.0
22 |
23 | ## null
24 |
25 | ### Patch Changes
26 |
27 | - Updated dependencies
28 | - vite-plugin-vercel@5.0.5
29 |
30 | ## null
31 |
32 | ### Patch Changes
33 |
34 | - Updated dependencies
35 | - vite-plugin-vercel@5.0.4
36 |
37 | ## null
38 |
39 | ### Patch Changes
40 |
41 | - Updated dependencies
42 | - vite-plugin-vercel@5.0.3
43 |
44 | ## null
45 |
46 | ### Patch Changes
47 |
48 | - vite-plugin-vercel@5.0.2
49 |
50 | ## null
51 |
52 | ### Patch Changes
53 |
54 | - vite-plugin-vercel@5.0.1
55 |
56 | ## null
57 |
58 | ### Patch Changes
59 |
60 | - Updated dependencies
61 | - vite-plugin-vercel@5.0.0
62 |
63 | ## null
64 |
65 | ### Patch Changes
66 |
67 | - Updated dependencies
68 | - vite-plugin-vercel@4.0.2
69 |
70 | ## null
71 |
72 | ### Patch Changes
73 |
74 | - Updated dependencies
75 | - vite-plugin-vercel@4.0.1
76 |
77 | ## null
78 |
79 | ### Patch Changes
80 |
81 | - Updated dependencies
82 | - vite-plugin-vercel@4.0.0
83 |
84 | ## null
85 |
86 | ### Patch Changes
87 |
88 | - Updated dependencies
89 | - vite-plugin-vercel@3.0.2
90 |
91 | ## null
92 |
93 | ### Patch Changes
94 |
95 | - Updated dependencies [8e7cad4]
96 | - vite-plugin-vercel@3.0.1
97 |
98 | ## null
99 |
100 | ### Patch Changes
101 |
102 | - Updated dependencies [ec8dcba]
103 | - vite-plugin-vercel@3.0.0
104 |
105 | ## null
106 |
107 | ### Patch Changes
108 |
109 | - Updated dependencies
110 | - vite-plugin-vercel@2.0.1
111 |
112 | ## null
113 |
114 | ### Patch Changes
115 |
116 | - Updated dependencies
117 | - vite-plugin-vercel@2.0.0
118 |
119 | ## null
120 |
121 | ### Patch Changes
122 |
123 | - Updated dependencies
124 | - vite-plugin-vercel@1.0.0
125 |
126 | ## null
127 |
128 | ### Patch Changes
129 |
130 | - Updated dependencies
131 | - vite-plugin-vercel@0.3.7
132 |
133 | ## null
134 |
135 | ### Patch Changes
136 |
137 | - Updated dependencies
138 | - vite-plugin-vercel@0.3.6
139 |
140 | ## null
141 |
142 | ### Patch Changes
143 |
144 | - Updated dependencies
145 | - vite-plugin-vercel@0.3.5
146 |
147 | ## null
148 |
149 | ### Patch Changes
150 |
151 | - Updated dependencies
152 | - vite-plugin-vercel@1.0.0
153 |
154 | ## null
155 |
156 | ### Patch Changes
157 |
158 | - Updated dependencies
159 | - vite-plugin-vercel@0.3.3
160 |
161 | ## null
162 |
163 | ### Patch Changes
164 |
165 | - Updated dependencies
166 | - vite-plugin-vercel@0.3.2
167 |
168 | ## null
169 |
170 | ### Patch Changes
171 |
172 | - Updated dependencies
173 | - vite-plugin-vercel@0.3.1
174 |
175 | ## null
176 |
177 | ### Patch Changes
178 |
179 | - Updated dependencies
180 | - vite-plugin-vercel@0.3.0
181 |
182 | ## null
183 |
184 | ### Patch Changes
185 |
186 | - Updated dependencies
187 | - vite-plugin-vercel@0.2.2
188 |
189 | ## null
190 |
191 | ### Patch Changes
192 |
193 | - Updated dependencies
194 | - vite-plugin-vercel@0.2.1
195 |
--------------------------------------------------------------------------------
/packages/vercel/src/prerender.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import path from "node:path";
3 | import type { Rewrite } from "@vercel/routing-utils";
4 | import type { ResolvedConfig } from "vite";
5 | import { copyDir } from "./helpers";
6 | import { type VercelOutputPrerenderConfig, vercelOutputPrerenderConfigSchema } from "./schemas/config/prerender-config";
7 | import type { VercelOutputIsr, ViteVercelPrerenderRoute } from "./types";
8 | import { getOutput } from "./utils";
9 |
10 | export function execPrerender(
11 | resolvedConfig: ResolvedConfig,
12 | ): ViteVercelPrerenderRoute | Promise {
13 | const prerender = resolvedConfig.vercel?.prerender;
14 |
15 | if (prerender === false) {
16 | return;
17 | }
18 |
19 | return prerender?.(resolvedConfig);
20 | }
21 |
22 | // FIXME { group } will be made optional https://github.com/orgs/vercel/discussions/577#discussioncomment-2759412
23 | let group = 1;
24 |
25 | export async function writePrerenderConfig(
26 | resolvedConfig: ResolvedConfig,
27 | destination: string,
28 | isr: VercelOutputPrerenderConfig,
29 | ): Promise {
30 | const parsed = path.parse(destination);
31 |
32 | const outfile = path.join(getOutput(resolvedConfig, "functions"), parsed.dir, `${parsed.name}.prerender-config.json`);
33 |
34 | await fs.mkdir(path.join(getOutput(resolvedConfig, "functions"), parsed.dir), { recursive: true });
35 |
36 | await fs.writeFile(
37 | outfile,
38 | JSON.stringify(
39 | vercelOutputPrerenderConfigSchema.parse({
40 | group: group++,
41 | ...isr,
42 | }),
43 | undefined,
44 | 2,
45 | ),
46 | "utf-8",
47 | );
48 | }
49 |
50 | export function getPrerenderSymlinkInfo(resolvedConfig: ResolvedConfig, destination: string, target: string) {
51 | const parsed = path.parse(destination);
52 | const targetParsed = path.parse(target);
53 |
54 | return {
55 | target: path.join(getOutput(resolvedConfig, "functions"), targetParsed.dir, `${targetParsed.name}.func`),
56 | link: path.join(getOutput(resolvedConfig, "functions"), parsed.dir, `${parsed.name}.func`),
57 | };
58 | }
59 |
60 | export async function buildPrerenderConfigs(
61 | resolvedConfig: ResolvedConfig,
62 | extractedIsr: Record,
63 | ): Promise> {
64 | const isr = Object.assign({}, extractedIsr, await getIsrConfig(resolvedConfig));
65 |
66 | const entries = Object.entries(isr);
67 | const rewrites: Rewrite[] = [];
68 |
69 | for (const [destination, { symlink, route, ...isr }] of entries) {
70 | await writePrerenderConfig(resolvedConfig, destination, isr);
71 | if (symlink) {
72 | const info = getPrerenderSymlinkInfo(resolvedConfig, destination, symlink);
73 | // FIXME symlinks are currently broken https://github.com/orgs/vercel/discussions/577#discussioncomment-2767120
74 | // await fs.symlink(
75 | // path.relative(path.dirname(info.link), info.target),
76 | // info.link,
77 | // );
78 | await copyDir(info.target, info.link);
79 | }
80 | if (route) {
81 | rewrites.push({
82 | source: `(${route})`,
83 | destination: `${destination}/?__original_path=$1`,
84 | });
85 | }
86 | }
87 |
88 | return rewrites;
89 | }
90 |
91 | async function getIsrConfig(resolvedConfig: ResolvedConfig): Promise> {
92 | const isr = resolvedConfig.vercel?.isr ?? {};
93 | if (typeof isr === "function") {
94 | return await isr();
95 | }
96 | return isr;
97 | }
98 |
--------------------------------------------------------------------------------
/packages/vike-integration/README.md:
--------------------------------------------------------------------------------
1 | # @vite-plugin-vercel/vike
2 |
3 | [`vike`](https://github.com/vikejs/vike) integration for `vite-plugin-vercel`.
4 |
5 | - Versions `>=0.3.3` are compatible with vike@0.4.x and above
6 | - Versions `0.1.x` are compatible with vike@0.4.x
7 | - Versions `0.0.x` are compatible with vike@0.3.x
8 |
9 | ## Features
10 |
11 | - [Support for ISR/Prerender Functions](#isrprerender-functions)
12 | - [Route strings](https://vike.dev/route-string) and [filesystem routing](https://vike.dev/filesystem-routing) are compiled to [routes rules](https://vercel.com/docs/build-output-api/v3#build-output-configuration/supported-properties/routes)
13 | - A Serverless Function is created by default to handle SSR route. No need to [manually create it](https://github.com/vikejs/vike_vercel/blob/main/api/ssr.js)
14 | - If you need to customize the Function, [some helpers are available](#custom-serverless-function-for-vike)
15 |
16 | ## Usage
17 |
18 | Install `vite-plugin-vercel` and `@vite-plugin-vercel/vike` and make sure only `vite-plugin-vercel` is added as a vite plugin.
19 |
20 | `vite-plugin-vercel` will auto load `@vite-plugin-vercel/vike` when necessary.
21 |
22 | ```ts
23 | // vite.config.ts
24 | import { defineConfig } from 'vite';
25 | import ssr from 'vike/plugin';
26 | import vercel from 'vite-plugin-vercel';
27 |
28 | export default defineConfig(async ({ command, mode }) => {
29 | return {
30 | plugins: [ssr(), vercel()],
31 | };
32 | });
33 | ```
34 |
35 | ### ISR/Prerender Functions
36 |
37 | Official documentation: https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions
38 |
39 | :warning: Pages with [route function](https://vike.dev/route-function) are not compatible with ISR. A warning will be shown if this occurs.
40 |
41 | #### vike 0.4.x
42 |
43 | Take any of your `.page` file (not `.page.server`) and add the following export:
44 |
45 | ```ts
46 | // Now this page is a Prerender Function, meaning that it will be cached on Edge network for 15 seconds.
47 | // Check official documentation for further details on how it works.
48 | export const isr = { expiration: 15 };
49 | ```
50 |
51 | #### vike V1 design
52 |
53 | Take any of your [page config file](https://vike.dev/config), and add the following configuration:
54 |
55 | ```ts
56 | import type { Config } from 'vike/types';
57 |
58 | export default {
59 | // Now this page is a Prerender Function, meaning that it will be cached on Edge network for 15 seconds.
60 | // Check official documentation for further details on how it works.
61 | isr: { expiration: 15 },
62 | } satisfies Config;
63 | ```
64 |
65 | ### Custom Serverless Function for vike
66 |
67 | By default, a Serverless Function is created to handle all SSR routes.
68 | If for any reason you need to customize it, some tools are available:
69 |
70 | ```ts
71 | import type { VercelRequest, VercelResponse } from '@vercel/node';
72 | import { renderPage } from 'vike/server';
73 | import {
74 | getDefaultEmptyResponseHandler,
75 | // higly recommended to use at least this one, as it handles some internals
76 | // that overrides `request.url`
77 | getDefaultPageContextInit,
78 | getDefaultResponseHandler,
79 | } from '@vite-plugin-vercel/vike/helpers';
80 |
81 | export default async function handler(
82 | request: VercelRequest,
83 | response: VercelResponse,
84 | ) {
85 | // pageContextInit.url is not necessarily equal to request.url.
86 | // It is required for `renderPage` to work properly.
87 | const pageContextInit = getDefaultPageContextInit(request);
88 | const pageContext = await renderPage(pageContextInit);
89 | const { httpResponse } = pageContext;
90 |
91 | if (!httpResponse) {
92 | return getDefaultEmptyResponseHandler(response);
93 | }
94 |
95 | return getDefaultResponseHandler(response, httpResponse);
96 | }
97 | ```
98 |
--------------------------------------------------------------------------------
/examples/demo/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # vite-plugin-vercel-demo-v1
2 |
3 | ## null
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies
8 | - @vite-plugin-vercel/vike@7.0.0
9 | - vite-plugin-vercel@7.0.0
10 |
11 | ## null
12 |
13 | ### Patch Changes
14 |
15 | - Updated dependencies
16 | - vite-plugin-vercel@6.0.1
17 | - @vite-plugin-vercel/vike@6.0.1
18 |
19 | ## null
20 |
21 | ### Patch Changes
22 |
23 | - Updated dependencies
24 | - @vite-plugin-vercel/vike@6.0.0
25 | - vite-plugin-vercel@6.0.0
26 |
27 | ## null
28 |
29 | ### Patch Changes
30 |
31 | - Updated dependencies
32 | - vite-plugin-vercel@5.0.5
33 | - @vite-plugin-vercel/vike@5.0.3
34 |
35 | ## null
36 |
37 | ### Patch Changes
38 |
39 | - Updated dependencies
40 | - vite-plugin-vercel@5.0.4
41 | - @vite-plugin-vercel/vike@5.0.2
42 |
43 | ## null
44 |
45 | ### Patch Changes
46 |
47 | - Updated dependencies
48 | - vite-plugin-vercel@5.0.3
49 | - @vite-plugin-vercel/vike@5.0.2
50 |
51 | ## null
52 |
53 | ### Patch Changes
54 |
55 | - Updated dependencies
56 | - @vite-plugin-vercel/vike@5.0.2
57 | - vite-plugin-vercel@5.0.2
58 |
59 | ## null
60 |
61 | ### Patch Changes
62 |
63 | - Updated dependencies
64 | - @vite-plugin-vercel/vike@5.0.1
65 | - vite-plugin-vercel@5.0.1
66 |
67 | ## null
68 |
69 | ### Patch Changes
70 |
71 | - Updated dependencies
72 | - vite-plugin-vercel@5.0.0
73 | - @vite-plugin-vercel/vike@5.0.0
74 |
75 | ## null
76 |
77 | ### Patch Changes
78 |
79 | - Updated dependencies
80 | - vite-plugin-vercel@4.0.2
81 | - @vite-plugin-vercel/vike@4.0.2
82 |
83 | ## null
84 |
85 | ### Patch Changes
86 |
87 | - Updated dependencies
88 | - Updated dependencies
89 | - @vite-plugin-vercel/vike@4.0.1
90 | - vite-plugin-vercel@4.0.1
91 |
92 | ## null
93 |
94 | ### Patch Changes
95 |
96 | - Updated dependencies
97 | - vite-plugin-vercel@4.0.0
98 | - @vite-plugin-vercel/vike@4.0.0
99 |
100 | ## null
101 |
102 | ### Patch Changes
103 |
104 | - Updated dependencies
105 | - vite-plugin-vercel@3.0.2
106 | - @vite-plugin-vercel/vike@3.0.1
107 |
108 | ## null
109 |
110 | ### Patch Changes
111 |
112 | - Updated dependencies [8e7cad4]
113 | - @vite-plugin-vercel/vike@3.0.1
114 | - vite-plugin-vercel@3.0.1
115 |
116 | ## null
117 |
118 | ### Patch Changes
119 |
120 | - Updated dependencies [ec8dcba]
121 | - @vite-plugin-vercel/vike@3.0.0
122 | - vite-plugin-vercel@3.0.0
123 |
124 | ## null
125 |
126 | ### Patch Changes
127 |
128 | - Updated dependencies
129 | - vite-plugin-vercel@2.0.1
130 | - @vite-plugin-vercel/vike@2.0.0
131 |
132 | ## null
133 |
134 | ### Patch Changes
135 |
136 | - Updated dependencies
137 | - vite-plugin-vercel@2.0.0
138 | - @vite-plugin-vercel/vike@2.0.0
139 |
140 | ## null
141 |
142 | ### Patch Changes
143 |
144 | - Updated dependencies
145 | - vite-plugin-vercel@1.0.0
146 | - @vite-plugin-vercel/vike@1.0.0
147 |
148 | ## null
149 |
150 | ### Patch Changes
151 |
152 | - Updated dependencies
153 | - vite-plugin-vercel@0.3.7
154 | - @vite-plugin-vercel/vike@0.4.2
155 |
156 | ## null
157 |
158 | ### Patch Changes
159 |
160 | - Updated dependencies
161 | - vite-plugin-vercel@0.3.6
162 | - @vite-plugin-vercel/vike@0.4.1
163 |
164 | ## null
165 |
166 | ### Patch Changes
167 |
168 | - Updated dependencies
169 | - vite-plugin-vercel@0.3.5
170 | - @vite-plugin-vercel/vike@0.4.0
171 |
172 | ## null
173 |
174 | ### Patch Changes
175 |
176 | - Updated dependencies
177 | - @vite-plugin-vercel/vike@0.4.0
178 | - vite-plugin-vercel@1.0.0
179 |
180 | ## null
181 |
182 | ### Patch Changes
183 |
184 | - Updated dependencies
185 | - vite-plugin-vercel@0.3.3
186 | - @vite-plugin-vercel/vike@0.3.3
187 |
188 | ## null
189 |
190 | ### Patch Changes
191 |
192 | - Updated dependencies
193 | - vite-plugin-vercel@0.3.2
194 | - @vite-plugin-vercel/vike@0.3.3
195 |
--------------------------------------------------------------------------------
/packages/vercel/src/schemas/config/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Schema definition for `.vercel/output/config.json`
3 | * @see {@link https://vercel.com/docs/build-output-api/v3#build-output-configuration}
4 | */
5 |
6 | import { z } from "zod";
7 |
8 | const HasOrMissing = z
9 | .array(
10 | z.union([
11 | z
12 | .object({
13 | type: z.literal("host"),
14 | value: z.string(),
15 | })
16 | .strict(),
17 | z
18 | .object({
19 | type: z.literal("header"),
20 | key: z.string(),
21 | value: z.string().optional(),
22 | })
23 | .strict(),
24 | z
25 | .object({
26 | type: z.literal("cookie"),
27 | key: z.string(),
28 | value: z.string().optional(),
29 | })
30 | .strict(),
31 | z
32 | .object({
33 | type: z.literal("query"),
34 | key: z.string(),
35 | value: z.string().optional(),
36 | })
37 | .strict(),
38 | ]),
39 | )
40 | .optional();
41 |
42 | export const vercelOutputConfigSchema = z
43 | .object({
44 | version: z.literal(3),
45 | routes: z
46 | .array(
47 | z.union([
48 | z
49 | .object({
50 | src: z.string(),
51 | dest: z.string().optional(),
52 | headers: z.record(z.string()).optional(),
53 | methods: z.array(z.string()).optional(),
54 | status: z.number().int().positive().optional(),
55 | continue: z.boolean().optional(),
56 | check: z.boolean().optional(),
57 | missing: HasOrMissing,
58 | has: HasOrMissing,
59 | locale: z
60 | .object({
61 | redirect: z.record(z.string()).optional(),
62 | cookie: z.string().optional(),
63 | })
64 | .strict()
65 | .optional(),
66 | middlewarePath: z.string().optional(),
67 | })
68 | .strict(),
69 | z
70 | .object({
71 | handle: z.union([
72 | z.literal("rewrite"),
73 | z.literal("filesystem"),
74 | z.literal("resource"),
75 | z.literal("miss"),
76 | z.literal("hit"),
77 | z.literal("error"),
78 | ]),
79 | src: z.string().optional(),
80 | dest: z.string().optional(),
81 | status: z.number().optional(),
82 | })
83 | .strict(),
84 | ]),
85 | )
86 | .optional(),
87 | images: z
88 | .object({
89 | sizes: z.tuple([z.number().int().positive(), z.number().int().positive()]),
90 | domains: z.array(z.string()).nonempty().optional(),
91 | minimumCacheTTL: z.number().int().positive().optional(),
92 | formats: z
93 | .union([z.literal("image/avif"), z.literal("image/webp")])
94 | .array()
95 | .nonempty()
96 | .optional(),
97 | dangerouslyAllowSVG: z.boolean().optional(),
98 | contentSecurityPolicy: z.string().optional(),
99 | })
100 | .strict()
101 | .optional(),
102 | wildcard: z
103 | .array(
104 | z
105 | .object({
106 | domain: z.string(),
107 | value: z.string(),
108 | })
109 | .strict(),
110 | )
111 | .optional(),
112 | overrides: z
113 | .record(
114 | z
115 | .object({
116 | path: z.string().optional(),
117 | contentType: z.string().optional(),
118 | })
119 | .strict(),
120 | )
121 | .optional(),
122 | cache: z.array(z.string()).optional(),
123 | })
124 | .strict();
125 |
126 | export type VercelOutputConfig = z.infer;
127 |
--------------------------------------------------------------------------------
/examples/demo/tests/05-vike/fs.test.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import glob from "fast-glob";
3 | import { describe, expect, it } from "vitest";
4 |
5 | describe("fs", () => {
6 | const buildManifest = require("../../dist/assets.json");
7 |
8 | const generatedFiles = Array.from(
9 | new Set(
10 | Object.values(buildManifest)
11 | .filter((e: any): e is any => Boolean(e.file))
12 | .flatMap((e) => [e.file, ...(e.assets ?? []), ...(e.css ?? [])])
13 | .filter((f) => f.startsWith("assets/")),
14 | ),
15 | );
16 |
17 | const expected = [
18 | "/config.json",
19 | "/functions/api/name/[name].func/.vc-config.json",
20 | "/functions/api/name/[name].func/index.mjs",
21 | "/functions/api/page.func/index.mjs",
22 | "/functions/api/page.func/.vc-config.json",
23 | "/functions/api/post.func/index.mjs",
24 | "/functions/api/post.func/.vc-config.json",
25 | "/functions/edge.func/index.js",
26 | "/functions/edge.func/.vc-config.json",
27 | "/functions/og-node.func/index.mjs",
28 | "/functions/og-node.func/.vc-config.json",
29 | "/functions/og-node.func/noto-sans-v27-latin-regular.ttf",
30 | "/functions/og-node.func/Roboto-Regular.ttf",
31 | "/functions/og-node.func/resvg.wasm",
32 | "/functions/og-node.func/yoga.wasm",
33 | "/functions/og-edge.func/index.js",
34 | "/functions/og-edge.func/.vc-config.json",
35 | "/functions/og-edge.func/noto-sans-v27-latin-regular.ttf",
36 | "/functions/og-edge.func/resvg.wasm",
37 | "/functions/og-edge.func/yoga.wasm",
38 | // ISR + Static pages
39 | "/functions/ssr_.func/index.mjs",
40 | "/functions/ssr_.func/.vc-config.json",
41 | "/static/404.html",
42 | "/static/index.html",
43 | "/static/index.pageContext.json",
44 | "/static/static/index.html",
45 | "/static/static/index.pageContext.json",
46 | "/static/catch-all/a/b/c/index.html",
47 | "/static/catch-all/a/b/c/index.pageContext.json",
48 | "/static/catch-all/a/d/index.html",
49 | "/static/catch-all/a/d/index.pageContext.json",
50 | "/static/function/a/index.html",
51 | "/static/function/a/index.pageContext.json",
52 | "/static/named/id-1/index.html",
53 | "/static/named/id-1/index.pageContext.json",
54 | "/static/named/id-2/index.html",
55 | "/static/named/id-2/index.pageContext.json",
56 | "/static/test.html",
57 | /\/functions\/pages\/catch-all-([^\/]+?)\.prerender-config\.json/,
58 | /\/functions\/pages\/catch-all-([^\/]+?)\.func\/index\.mjs/,
59 | /\/functions\/pages\/catch-all-([^\/]+?)\.func\/\.vc-config\.json/,
60 | /\/functions\/pages\/isr-([^\/]+?)\.prerender-config\.json/,
61 | /\/functions\/pages\/isr-([^\/]+?)\.func\/index\.mjs/,
62 | /\/functions\/pages\/isr-([^\/]+?)\.func\/\.vc-config\.json/,
63 | /\/functions\/pages\/named-([^\/]+?)\.prerender-config\.json/,
64 | /\/functions\/pages\/named-([^\/]+?)\.func\/index\.mjs/,
65 | /\/functions\/pages\/named-([^\/]+?)\.func\/\.vc-config\.json/,
66 | // vike-edge
67 | /\/functions\/pages\/vike-edge-edge-([^\/]+?)\.func\/index\.js/,
68 | /\/functions\/pages\/vike-edge-edge-([^\/]+?)\.func\/\.vc-config\.json/,
69 | ...generatedFiles.map((f) => `/static/${f}`),
70 | ];
71 |
72 | it("should generate the right files", async () => {
73 | const dir = path.join(__dirname, "../../.vercel/output");
74 | const entries = await glob(`${dir}/**`, { dot: true });
75 | let mappedEntries = entries.map((e) => e.replace(dir, "")).filter((e) => !e.startsWith("/_ignore"));
76 |
77 | mappedEntries = Array.from(new Set(mappedEntries));
78 |
79 | expect(entries).toHaveLength(expected.length);
80 | for (const entry of mappedEntries) {
81 | expect(entry).toSatisfy((elt: string) => {
82 | for (const exp of expected) {
83 | if (typeof exp === "string") {
84 | if (exp === elt) return true;
85 | } else {
86 | const match = elt.match(exp);
87 | if (match) return true;
88 | }
89 | }
90 | console.error("no match found for", elt);
91 | return false;
92 | });
93 | }
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/examples/demo/tests/05-vike/config.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, it } from "vitest";
2 | import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
3 | import { testSchema } from "../common/helpers";
4 | import { prepareTestJsonFileContent } from "./utils";
5 |
6 | prepareTestJsonFileContent("config.json", (context) => {
7 | testSchema(context, vercelOutputConfigSchema);
8 |
9 | it("should have defaults routes only", () => {
10 | const expected = [
11 | {
12 | src: "^/vike-edge$",
13 | headers: {
14 | "X-VitePluginVercel-Test": "test",
15 | },
16 | continue: true,
17 | },
18 | {
19 | src: "^/vike-edge/index\\.pageContext\\.json$",
20 | headers: {
21 | "X-VitePluginVercel-Test": "test",
22 | },
23 | continue: true,
24 | },
25 | {
26 | src: "^/api/page$",
27 | headers: { "X-VitePluginVercel-Test": "test" },
28 | continue: true,
29 | },
30 | {
31 | headers: { Location: "/$1" },
32 | src: "^/(?:(.+)/)?index(?:\\.html)?/?$",
33 | status: 308,
34 | },
35 | {
36 | headers: { Location: "/$1" },
37 | src: "^/(.*)\\.html/?$",
38 | status: 308,
39 | },
40 | { handle: "filesystem" },
41 | {
42 | src: "^/edge(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
43 | dest: "/edge/$1",
44 | check: true,
45 | },
46 | {
47 | src: "^/og-node(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
48 | dest: "/og-node/$1",
49 | check: true,
50 | },
51 | {
52 | src: "^/og-edge(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?$",
53 | dest: "/og-edge/$1",
54 | check: true,
55 | },
56 | {
57 | src: "^(/vike-edge(?:/index\\.pageContext\\.json)?)$",
58 | dest: expect.stringMatching("/pages/vike-edge-edge-([^/]+?)/\\?__original_path=\\$1"),
59 | check: true,
60 | },
61 | {
62 | check: true,
63 | src: "^/api/page$",
64 | dest: "/api/page",
65 | },
66 | {
67 | check: true,
68 | src: "^/api/post$",
69 | dest: "/api/post",
70 | },
71 | {
72 | check: true,
73 | src: "^/api/name(?:/([^/]+?))$",
74 | dest: "/api/name/[name]?name=$1",
75 | },
76 | {
77 | check: true,
78 | src: "^(/catch-all/.+?(?:/index\\.pageContext\\.json)?)$",
79 | dest: expect.stringMatching("/pages/catch-all-([^/]+?)/\\?__original_path=\\$1"),
80 | },
81 | {
82 | check: true,
83 | src: "^(/isr(?:/index\\.pageContext\\.json)?)$",
84 | dest: expect.stringMatching("/pages/isr-([^/]+?)/\\?__original_path=\\$1"),
85 | },
86 | {
87 | check: true,
88 | src: "^(/named/[^/]+(?:/index\\.pageContext\\.json)?)$",
89 | dest: expect.stringMatching("/pages/named-([^/]+?)/\\?__original_path=\\$1"),
90 | },
91 | { check: true, dest: "/ssr_/?__original_path=$1", src: "^((?!/api).*)$" },
92 | ];
93 |
94 | expect((context.file as any).routes).toHaveLength(expected.length);
95 | for (const route of expected) {
96 | expect((context.file as any).routes).toContainEqual(route);
97 | }
98 | expect((context.file as any).overrides).toMatchObject({
99 | // '404.html': {
100 | // path: '404',
101 | // },
102 | "catch-all/a/b/c/index.html": {
103 | path: "catch-all/a/b/c",
104 | },
105 | "catch-all/a/d/index.html": {
106 | path: "catch-all/a/d",
107 | },
108 | "function/a/index.html": {
109 | path: "function/a",
110 | },
111 | "index.html": {
112 | path: "",
113 | },
114 | "named/id-1/index.html": {
115 | path: "named/id-1",
116 | },
117 | "named/id-2/index.html": {
118 | path: "named/id-2",
119 | },
120 | "static/index.html": {
121 | path: "static",
122 | },
123 | });
124 | expect(Object.keys(context.file as any).sort()).toEqual(["version", "overrides", "routes"].sort());
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/packages/vercel/src/config.ts:
--------------------------------------------------------------------------------
1 | import type { ResolvedConfig } from "vite";
2 | import path from "node:path";
3 | import { getOutput } from "./utils";
4 | import { type VercelOutputConfig, vercelOutputConfigSchema } from "./schemas/config/config";
5 | import fs from "node:fs/promises";
6 | import {
7 | getTransformedRoutes,
8 | type Header,
9 | mergeRoutes,
10 | normalizeRoutes,
11 | type Rewrite,
12 | type Route,
13 | } from "@vercel/routing-utils";
14 | import type { ViteVercelRewrite } from "./types";
15 |
16 | function reorderEnforce(arr: T[]) {
17 | return [
18 | ...arr.filter((r) => r.enforce === "pre"),
19 | ...arr.filter((r) => !r.enforce),
20 | ...arr.filter((r) => r.enforce === "post"),
21 | ];
22 | }
23 |
24 | export function getConfig(
25 | resolvedConfig: ResolvedConfig,
26 | rewrites?: ViteVercelRewrite[],
27 | overrides?: VercelOutputConfig["overrides"],
28 | headers?: Header[],
29 | ): VercelOutputConfig {
30 | const _rewrites: ViteVercelRewrite[] = [
31 | // User provided config always comes first
32 | ...(resolvedConfig.vercel?.rewrites ?? []),
33 | ...(rewrites ?? []),
34 | ];
35 |
36 | const _enforcedRewrites = reorderEnforce(_rewrites).map((r) => {
37 | // optional catch-all
38 | // :[...optionalCatchAll] -> :catchAll* (global)
39 | r.source = r.source.replaceAll(/:\[(\.\.\.)(.*)\]/g, ":$2*");
40 |
41 | // catch-all
42 | // :...catchAll -> :catchAll+ (global)
43 | r.source = r.source.replaceAll(/:(\.\.\.)(.*)/g, ":$2+");
44 |
45 | return r;
46 | });
47 |
48 | const { routes, error } = getTransformedRoutes({
49 | cleanUrls: resolvedConfig.vercel?.cleanUrls ?? true,
50 | trailingSlash: resolvedConfig.vercel?.trailingSlash,
51 | rewrites: _enforcedRewrites,
52 | redirects: resolvedConfig.vercel?.redirects ? reorderEnforce(resolvedConfig.vercel?.redirects) : undefined,
53 | headers,
54 | });
55 |
56 | if (error) {
57 | throw error;
58 | }
59 |
60 | if (
61 | resolvedConfig.vercel?.config?.routes &&
62 | resolvedConfig.vercel.config.routes.length > 0 &&
63 | !resolvedConfig.vercel.config.routes.every((r) => "continue" in r && r.continue)
64 | ) {
65 | console.warn(
66 | 'Did you forget to add `"continue": true` to your routes? See https://vercel.com/docs/build-output-api/v3/configuration#source-route\n' +
67 | "If not, it is discouraged to use `vercel.config.routes` to override routes. " +
68 | "Prefer using `vercel.rewrites` and `vercel.redirects`.",
69 | );
70 | }
71 |
72 | let userRoutes: Route[] = [];
73 | let buildRoutes: Route[] = [];
74 |
75 | if (resolvedConfig.vercel?.config?.routes) {
76 | const norm = normalizeRoutes(resolvedConfig.vercel.config.routes);
77 |
78 | if (norm.error) {
79 | throw norm.error;
80 | }
81 |
82 | userRoutes = norm.routes ?? [];
83 | }
84 |
85 | if (routes) {
86 | const norm = normalizeRoutes(routes);
87 |
88 | if (norm.error) {
89 | throw norm.error;
90 | }
91 |
92 | buildRoutes = norm.routes ?? [];
93 | }
94 |
95 | const cleanRoutes = mergeRoutes({
96 | userRoutes,
97 | builds: [
98 | {
99 | use: "@vercel/node",
100 | entrypoint: "index.js",
101 | routes: buildRoutes,
102 | },
103 | ],
104 | });
105 |
106 | return vercelOutputConfigSchema.parse({
107 | version: 3,
108 | ...resolvedConfig.vercel?.config,
109 | routes: cleanRoutes,
110 | overrides: {
111 | ...resolvedConfig.vercel?.config?.overrides,
112 | ...overrides,
113 | },
114 | });
115 | }
116 |
117 | export function getConfigDestination(resolvedConfig: ResolvedConfig) {
118 | return path.join(getOutput(resolvedConfig), "config.json");
119 | }
120 |
121 | export async function writeConfig(
122 | resolvedConfig: ResolvedConfig,
123 | rewrites?: Rewrite[],
124 | overrides?: VercelOutputConfig["overrides"],
125 | headers?: Header[],
126 | ): Promise {
127 | await fs.writeFile(
128 | getConfigDestination(resolvedConfig),
129 | JSON.stringify(getConfig(resolvedConfig, rewrites, overrides, headers), undefined, 2),
130 | "utf-8",
131 | );
132 | }
133 |
--------------------------------------------------------------------------------
/examples/demo/renderer/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
37 |
--------------------------------------------------------------------------------
/packages/vike-integration/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @vite-plugin-vercel/vike
2 |
3 | ## 9.1.0
4 |
5 | ### Minor Changes
6 |
7 | - fix: upgrade dependencies. Dropping support for node@18
8 |
9 | ## 9.0.7
10 |
11 | ### Patch Changes
12 |
13 | - feat: adding support for node@24
14 |
15 | ## 9.0.6
16 |
17 | ### MINOR BREAKING CHANGES
18 |
19 | - Update Vike to `0.4.229` or above.
20 |
21 | ## 9.0.5
22 |
23 | ### Patch Changes
24 |
25 | - 3f3dd9c: fix(vike): do not show route function warnings for non route function
26 | - 3f3dd9c: fix(vike): streaming error in \_error pages
27 |
28 | ## 9.0.4
29 |
30 | ### Patch Changes
31 |
32 | - Improve compatibility with latest Vike versions
33 |
34 | ## 9.0.3
35 |
36 | ### Patch Changes
37 |
38 | - Update peerDependencies to include Vite6
39 |
40 | ## 9.0.2
41 |
42 | ### Patch Changes
43 |
44 | - Upgrade dependencies
45 | - fix CVE-2024-45296. See https://github.com/advisories/GHSA-9wv6-86v2-598j
46 |
47 | ## 9.0.1
48 |
49 | ### Patch Changes
50 |
51 | - feat: Vike now support headers in +config.ts
52 |
53 | ## 9.0.0
54 |
55 | ### Minor Changes
56 |
57 | - feat(vike): support edge config in +config.ts file
58 |
59 | ### Patch Changes
60 |
61 | - Updated dependencies
62 | - vite-plugin-vercel@9.0.0
63 |
64 | ## 8.0.1
65 |
66 | ### Patch Changes
67 |
68 | - avoid double build of additionnal endpoint
69 | - Updated dependencies
70 | - vite-plugin-vercel@8.0.1
71 |
72 | ## 8.0.0
73 |
74 | ### Minor Changes
75 |
76 | - Target node@18 by default
77 |
78 | ### Patch Changes
79 |
80 | - Updated dependencies
81 | - vite-plugin-vercel@8.0.0
82 |
83 | ## 7.0.1
84 |
85 | ### Patch Changes
86 |
87 | - Better support for servers like express
88 | - Updated dependencies
89 | - vite-plugin-vercel@7.0.1
90 |
91 | ## 7.0.0
92 |
93 | ### Minor Changes
94 |
95 | - Support for vike@0.4.172
96 |
97 | ### Patch Changes
98 |
99 | - vite-plugin-vercel@7.0.0
100 |
101 | ## 6.0.1
102 |
103 | ### Patch Changes
104 |
105 | - Update target to es2022
106 | - Updated dependencies
107 | - vite-plugin-vercel@6.0.1
108 |
109 | ## 6.0.0
110 |
111 | ### Minor Changes
112 |
113 | - add support for supportsResponseStreaming
114 |
115 | ### Patch Changes
116 |
117 | - Updated dependencies
118 | - vite-plugin-vercel@6.0.0
119 |
120 | ## 5.0.3
121 |
122 | ### Patch Changes
123 |
124 | - Fix warning when trying to read exports
125 | - Updated dependencies
126 | - vite-plugin-vercel@5.0.5
127 |
128 | ## 5.0.2
129 |
130 | ### Patch Changes
131 |
132 | - Update minimum peer dependency
133 | - vite-plugin-vercel@5.0.2
134 |
135 | ## 5.0.1
136 |
137 | ### Patch Changes
138 |
139 | - Migrate to last recommendations
140 | - vite-plugin-vercel@5.0.1
141 |
142 | ## 5.0.0
143 |
144 | ### Patch Changes
145 |
146 | - Updated dependencies
147 | - vite-plugin-vercel@5.0.0
148 |
149 | ## 4.0.2
150 |
151 | ### Patch Changes
152 |
153 | - Upgrade dependencies
154 | - Updated dependencies
155 | - vite-plugin-vercel@4.0.2
156 |
157 | ## 4.0.1
158 |
159 | ### Patch Changes
160 |
161 | - Fix **VITE_ASSETS_MAP** error
162 | - Updated dependencies
163 | - vite-plugin-vercel@4.0.1
164 |
165 | ## 4.0.0
166 |
167 | ### Patch Changes
168 |
169 | - Updated dependencies
170 | - vite-plugin-vercel@4.0.0
171 |
172 | ## 3.0.1
173 |
174 | ### Patch Changes
175 |
176 | - 8e7cad4: Add vite@5 in peerDependencies
177 | - Updated dependencies [8e7cad4]
178 | - vite-plugin-vercel@3.0.1
179 |
180 | ## 3.0.0
181 |
182 | ### Major Changes
183 |
184 | - ec8dcba: - Target node20.x
185 | - Upgrade all dependencies to their latest versions
186 | - Drops support for vike non-v1 design (still works with v2)
187 |
188 | ### Patch Changes
189 |
190 | - Updated dependencies [ec8dcba]
191 | - vite-plugin-vercel@3.0.0
192 |
193 | ## 2.0.0
194 |
195 | ### Patch Changes
196 |
197 | - Updated dependencies
198 | - vite-plugin-vercel@2.0.0
199 |
200 | ## 1.0.0
201 |
202 | ### Patch Changes
203 |
204 | - Updated dependencies
205 | - vite-plugin-vercel@1.0.0
206 |
207 | ## 0.4.2
208 |
209 | ### Patch Changes
210 |
211 | - Upgrade dependencies
212 | - Updated dependencies
213 | - vite-plugin-vercel@0.3.7
214 |
215 | ## 0.4.1
216 |
217 | ### Patch Changes
218 |
219 | - chore: remove esbuild verbose warnings
220 | - Updated dependencies
221 | - vite-plugin-vercel@0.3.6
222 |
223 | ## 0.4.0
224 |
225 | ### Minor Changes
226 |
227 | - Rename vite-plugin-vercel to Vike
228 |
229 | ## 0.3.3
230 |
231 | ### Patch Changes
232 |
233 | - Add support for vike V1 design
234 |
235 | ## 0.3.2
236 |
237 | ### Patch Changes
238 |
239 | - update dependencies
240 |
241 | ## 0.3.1
242 |
243 | ### Patch Changes
244 |
245 | - Rename Vike integration plugin to @vite-plugin-vercel/vike
246 |
247 | ## 0.2.2
248 |
249 | ### Patch Changes
250 |
251 | - Update documentation and upgrade dependencies
252 |
253 | ## 0.2.1
254 |
255 | ### Patch Changes
256 |
257 | - fix use case without vike
258 |
259 | ## 0.2.0
260 |
261 | ### Minor Changes
262 |
263 | - upgrade dependencies
264 |
265 | ### Patch Changes
266 |
267 | - support edge-light target
268 |
269 | ## 0.1.5
270 |
271 | ### Patch Changes
272 |
273 | - fix issue with win32 path
274 |
275 | ## 0.1.4
276 |
277 | ### Patch Changes
278 |
279 | - Upgrade dependencies. Fixes #9 and #10
280 |
281 | ## 0.1.3
282 |
283 | ### Patch Changes
284 |
285 | - Edge Functions support
286 |
287 | ## 0.1.2
288 |
289 | ### Patch Changes
290 |
291 | - Fix exports
292 |
293 | ## 0.1.1
294 |
295 | ### Patch Changes
296 |
297 | - Add missing helper file in bundle
298 |
299 | ## 0.1.0
300 |
301 | ### Minor Changes
302 |
303 | - Support for vike@0.4.x
304 |
305 | ### Patch Changes
306 |
307 | - Support for vite@3
308 |
309 | ## 0.0.9
310 |
311 | ### Patch Changes
312 |
313 | - Fix duplicate route entries
314 |
315 | ## 0.0.8
316 |
317 | ### Patch Changes
318 |
319 | - Support rewrites/redirects options (prefered over config.routes)
320 |
321 | ## 0.0.7
322 |
323 | ### Patch Changes
324 |
325 | - Change config syntax
326 |
327 | ## 0.0.6
328 |
329 | ### Patch Changes
330 |
331 | - Default SSR route excludes /api
332 |
333 | ## 0.0.5
334 |
335 | ### Patch Changes
336 |
337 | - isr config can now be overriden when using vike integration
338 |
339 | ## 0.0.4
340 |
341 | ### Patch Changes
342 |
343 | - Include templates folder
344 |
345 | ## 0.0.3
346 |
347 | ### Patch Changes
348 |
349 | - Update package.json metadata
350 |
351 | ## 0.0.2
352 |
353 | ### Patch Changes
354 |
355 | - Create a dedicated package for vike integration
356 |
--------------------------------------------------------------------------------
/packages/vercel/src/index.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import path from "node:path";
3 | import type { Plugin, PluginOption, ResolvedConfig } from "vite";
4 | import { buildEndpoints } from "./build";
5 | import { writeConfig } from "./config";
6 | import { copyDir } from "./helpers";
7 | import { buildPrerenderConfigs, execPrerender } from "./prerender";
8 | import type { ViteVercelPrerenderRoute } from "./types";
9 | import { getOutput, getPublic } from "./utils";
10 |
11 | export * from "./types";
12 |
13 | function vercelPluginCleanup(): Plugin {
14 | let resolvedConfig: ResolvedConfig;
15 |
16 | return {
17 | apply: "build",
18 | name: "vite-plugin-vercel:cleanup",
19 | enforce: "pre",
20 |
21 | configResolved(config) {
22 | resolvedConfig = config;
23 | },
24 | writeBundle: {
25 | order: "pre",
26 | sequential: true,
27 | async handler() {
28 | if (!resolvedConfig.build?.ssr) {
29 | // step 1: Clean .vercel/ouput dir
30 | await cleanOutputDirectory(resolvedConfig);
31 | }
32 | },
33 | },
34 | };
35 | }
36 |
37 | type Writeable = { -readonly [P in keyof T]: T[P] };
38 |
39 | function vercelPlugin(): Plugin {
40 | let resolvedConfig: Writeable;
41 | let vikeFound = false;
42 |
43 | return {
44 | apply: "build",
45 | name: "vite-plugin-vercel",
46 | enforce: "post",
47 |
48 | configResolved(config) {
49 | resolvedConfig = config;
50 | vikeFound = resolvedConfig.plugins.some((p) => p.name.match("^vite-plugin-ssr:|^vike:"));
51 |
52 | if (typeof resolvedConfig.vercel?.distContainsOnlyStatic === "undefined") {
53 | resolvedConfig.vercel ??= {};
54 | resolvedConfig.vercel.distContainsOnlyStatic = !vikeFound;
55 | }
56 | },
57 | writeBundle: {
58 | order: "post",
59 | sequential: true,
60 | async handler() {
61 | if (!resolvedConfig.build?.ssr) {
62 | // special case: Vike triggers a second build with --ssr
63 | // TODO: find a way to fix that in a more generic way
64 | if (vikeFound) {
65 | return;
66 | }
67 | }
68 |
69 | // step 2: Execute prerender
70 | const overrides = await execPrerender(resolvedConfig);
71 |
72 | // step 3: Compute overrides for static HTML files
73 | const userOverrides = await computeStaticHtmlOverrides(resolvedConfig);
74 |
75 | // step 4: Compile serverless functions to ".vercel/output/functions"
76 | const { rewrites, isr, headers } = await buildEndpoints(resolvedConfig);
77 |
78 | // step 5: Generate prerender config files
79 | rewrites.push(...(await buildPrerenderConfigs(resolvedConfig, isr)));
80 |
81 | // step 6: Generate config file
82 | await writeConfig(
83 | resolvedConfig,
84 | rewrites,
85 | {
86 | ...userOverrides,
87 | ...overrides,
88 | },
89 | headers,
90 | );
91 |
92 | // step 7: Copy dist folder to static
93 | await copyDistToStatic(resolvedConfig);
94 | },
95 | },
96 | };
97 | }
98 |
99 | async function cleanOutputDirectory(resolvedConfig: ResolvedConfig) {
100 | await fs.rm(getOutput(resolvedConfig), {
101 | recursive: true,
102 | force: true,
103 | });
104 |
105 | await fs.mkdir(getOutput(resolvedConfig), { recursive: true });
106 | }
107 |
108 | async function copyDistToStatic(resolvedConfig: ResolvedConfig) {
109 | if (resolvedConfig.vercel?.distContainsOnlyStatic) {
110 | await copyDir(resolvedConfig.build.outDir, getOutput(resolvedConfig, "static"));
111 | }
112 | }
113 |
114 | async function computeStaticHtmlOverrides(
115 | resolvedConfig: ResolvedConfig,
116 | ): Promise> {
117 | const staticAbsolutePath = getOutput(resolvedConfig, "static");
118 | const files = await getStaticHtmlFiles(staticAbsolutePath);
119 |
120 | // public files copied by vite by default https://vitejs.dev/guide/assets.html#the-public-directory
121 | const publicDir = getPublic(resolvedConfig);
122 | const publicFiles = await getStaticHtmlFiles(publicDir);
123 | files.push(...publicFiles.map((f) => f.replace(publicDir, staticAbsolutePath)));
124 |
125 | return files.reduce(
126 | (acc, curr) => {
127 | const relPath = path.relative(staticAbsolutePath, curr);
128 | const parsed = path.parse(relPath);
129 | const pathJoined = path.join(parsed.dir, parsed.name);
130 | acc[relPath] = {
131 | path: pathJoined,
132 | };
133 | return acc;
134 | },
135 | {} as NonNullable,
136 | );
137 | }
138 |
139 | async function getStaticHtmlFiles(src: string) {
140 | try {
141 | await fs.stat(src);
142 | } catch (e) {
143 | return [];
144 | }
145 |
146 | const entries = await fs.readdir(src, { withFileTypes: true });
147 | const htmlFiles: string[] = [];
148 |
149 | for (const entry of entries) {
150 | const srcPath = path.join(src, entry.name);
151 |
152 | entry.isDirectory()
153 | ? htmlFiles.push(...(await getStaticHtmlFiles(srcPath)))
154 | : srcPath.endsWith(".html")
155 | ? htmlFiles.push(srcPath)
156 | : undefined;
157 | }
158 |
159 | return htmlFiles;
160 | }
161 |
162 | /**
163 | * Auto import `@vite-plugin-vercel/vike` if it is part of dependencies.
164 | * Ensures that `vike/plugin` is also present to ensure predictable behavior
165 | */
166 |
167 | // biome-ignore lint/suspicious/noExplicitAny:
168 | async function tryImportVpvv(options: any) {
169 | try {
170 | // @ts-ignore
171 | await import("vike/plugin");
172 | // @ts-ignore
173 | const vpvv = await import("@vite-plugin-vercel/vike");
174 | return vpvv.default(options);
175 | } catch (e) {
176 | try {
177 | // @ts-ignore
178 | await import("vite-plugin-ssr/plugin");
179 | // @ts-ignore
180 | const vpvv = await import("@vite-plugin-vercel/vike");
181 | return vpvv.default(options);
182 | } catch (e) {
183 | return null;
184 | }
185 | }
186 | }
187 |
188 | // `smart` param only exist to circumvent a pnpm issue in dev
189 | // See https://github.com/pnpm/pnpm/issues/3697#issuecomment-1708687974
190 | // FIXME: Could be fixed by:
191 | // - shared-workspace-lockfile=false in .npmrc. See https://pnpm.io/npmrc#shared-workspace-lockfile
192 | // - Moving demo test in dedicated repo, with each a correct package.json
193 | // biome-ignore lint/suspicious/noExplicitAny:
194 | export default function allPlugins(options: any = {}): PluginOption[] {
195 | const { smart, ...rest } = options;
196 | return [vercelPluginCleanup(), vercelPlugin(), smart !== false ? tryImportVpvv(rest) : null];
197 | }
198 |
--------------------------------------------------------------------------------
/packages/vercel/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { Header, Redirect, Rewrite } from "@vercel/routing-utils";
2 | import type { BuildOptions, StdinOptions } from "esbuild";
3 | import type { ResolvedConfig } from "vite";
4 | import type { VercelOutputConfig } from "./schemas/config/config";
5 | import type { VercelOutputPrerenderConfig } from "./schemas/config/prerender-config";
6 | import type { VercelOutputVcConfig } from "./schemas/config/vc-config";
7 |
8 | export type { VercelOutputConfig, VercelOutputVcConfig, VercelOutputPrerenderConfig };
9 |
10 | export type ViteVercelRewrite = Rewrite & { enforce?: "pre" | "post" };
11 | export type ViteVercelRedirect = Redirect & { enforce?: "pre" | "post" };
12 |
13 | export type Awaitable = T | Promise;
14 |
15 | // Vite config for Vercel
16 |
17 | export interface ViteVercelConfig {
18 | /**
19 | * How long Functions should be allowed to run for every request, in seconds.
20 | * If left empty, default value for your plan will be used.
21 | */
22 | defaultMaxDuration?: number;
23 | /**
24 | * Default expiration time (in seconds) for prerender functions.
25 | * Defaults to 86400 seconds (24h).
26 | * @see {@link https://vercel.com/docs/concepts/next.js/incremental-static-regeneration}
27 | * @see {@link https://vercel.com/docs/build-output-api/v3#vercel-primitives/prerender-functions/configuration}
28 | */
29 | expiration?: number;
30 | /**
31 | * Also known as Server Side Generation, or SSG.
32 | * If present, this function is responsible to create static files in `.vercel/output/static`.
33 | * Defaults to `false`, which disables prerendering.
34 | */
35 | prerender?: ViteVercelPrerenderFn | false;
36 | /**
37 | * @see {@link https://vercel.com/docs/projects/project-configuration#rewrites}
38 | */
39 | rewrites?: ViteVercelRewrite[];
40 | /**
41 | * @see {@link https://vercel.com/docs/projects/project-configuration#headers}
42 | * @beta
43 | */
44 | headers?: Header[] | (() => Awaitable);
45 | /**
46 | * @see {@link https://vercel.com/docs/projects/project-configuration#redirects}
47 | */
48 | redirects?: ViteVercelRedirect[];
49 | /**
50 | * @see {@link https://vercel.com/docs/projects/project-configuration#cleanurls}
51 | */
52 | cleanUrls?: boolean;
53 | /**
54 | * @see {@link https://vercel.com/docs/projects/project-configuration#trailingslash}
55 | */
56 | trailingSlash?: boolean;
57 | /**
58 | * When true, the Serverless Function will stream the response to the client.
59 | * @see {@link https://vercel.com/docs/build-output-api/v3/primitives#serverless-function-configuration}
60 | */
61 | defaultSupportsResponseStreaming?: boolean;
62 | /**
63 | * By default, all `api/*` endpoints are compiled under `.vercel/output/functions/api/*.func`.
64 | * If others serverless functions need to be compiled under `.vercel/output/functions`, they should be added here.
65 | * For instance, a framework can leverage this to have a generic ssr endpoint
66 | * without requiring the user to write any code.
67 | *
68 | * @example
69 | * ```
70 | * {
71 | * additionalEndpoints: [
72 | * {
73 | * // can also be an Object representing an esbuild StdinOptions
74 | * source: '/path/to/file.ts',
75 | * // URL path of the handler, will be generated to `.vercel/output/functions/api/file.func/index.js`
76 | * destination: '/api/file',
77 | * }
78 | * ]
79 | * }
80 | * ```
81 | */
82 | additionalEndpoints?: (
83 | | ViteVercelApiEntry
84 | | (() => Awaitable)
85 | | (() => Awaitable)
86 | )[];
87 | /**
88 | * Advanced configuration to override .vercel/output/config.json
89 | * @see {@link https://vercel.com/docs/build-output-api/v3/configuration#configuration}
90 | * @protected
91 | */
92 | config?: Partial>;
93 | /**
94 | * ISR and SSG pages are mutually exclusive. If a page is found in both, ISR prevails.
95 | * Keys are path relative to .vercel/output/functions directory, either without extension,
96 | * or with `.prerender-config.json` extension.
97 | * If you have multiple isr configurations pointing to the same underlying function, you can leverage the `symlink`
98 | * property. See example below.
99 | *
100 | * @example
101 | * ```
102 | * // Here `ssr_` means that a function is available under `.vercel/output/functions/ssr_.func`
103 | * isr: {
104 | * '/pages/a': { expiration: 15, symlink: 'ssr_', route: '^/a/.*$' },
105 | * '/pages/b/c': { expiration: 15, symlink: 'ssr_', route: '^/b/c/.*$' },
106 | * '/pages/d': { expiration: 15, symlink: 'ssr_', route: '^/d$' },
107 | * '/pages/e': { expiration: 25 }
108 | * }
109 | * ```
110 | *
111 | * @protected
112 | */
113 | isr?: Record | (() => Awaitable>);
114 | /**
115 | * Defaults to `.vercel/output`. Mostly useful for testing purpose
116 | * @protected
117 | */
118 | outDir?: string;
119 | /**
120 | * By default, Vite generates static files under `dist` folder.
121 | * But usually, when used through a Framework, such as Vike,
122 | * this folder can contain anything, requiring custom integration.
123 | * Set this to false if you create a plugin for a Framework.
124 | */
125 | distContainsOnlyStatic?: boolean;
126 | }
127 |
128 | export interface VercelOutputIsr extends VercelOutputPrerenderConfig {
129 | symlink?: string;
130 | route?: string;
131 | }
132 |
133 | /**
134 | * Keys are path relative to .vercel/output/static directory
135 | */
136 | export type ViteVercelPrerenderRoute = VercelOutputConfig["overrides"];
137 | export type ViteVercelPrerenderFn = (resolvedConfig: ResolvedConfig) => Awaitable;
138 |
139 | export interface ViteVercelApiEntry {
140 | /**
141 | * Path to entry file, or stdin config
142 | */
143 | source: string | StdinOptions;
144 | /**
145 | * Relative to `.vercel/output/functions`, without extension
146 | */
147 | destination: string;
148 | /**
149 | * Override esbuild options
150 | */
151 | buildOptions?: BuildOptions;
152 | /**
153 | * @deprecated use `route` instead
154 | */
155 | addRoute?: boolean;
156 | /**
157 | * If `true`, guesses route for the function, and adds it to config.json (mimics defaults Vercel behavior).
158 | * If a string is provided, it will be equivalent to a `rewrites` rule.
159 | * Set to `false` to disable
160 | */
161 | route?: string | boolean;
162 | /**
163 | * Set to `true` to mark this function as an Edge Function
164 | */
165 | edge?: boolean;
166 | /**
167 | * Additional headers
168 | */
169 | headers?: Record | null;
170 | /**
171 | * ISR config
172 | */
173 | isr?: VercelOutputIsr;
174 | /**
175 | * When true, the Serverless Function will stream the response to the client
176 | */
177 | streaming?: boolean;
178 | }
179 |
--------------------------------------------------------------------------------
/packages/vercel/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # vite-plugin-vercel
2 |
3 | ## 9.1.1
4 |
5 | ### Patch Changes
6 |
7 | - fix: support recent node versions
8 |
9 | ## 9.1.0
10 |
11 | ### Minor Changes
12 |
13 | - fix: upgrade dependencies. Dropping support for node@18
14 |
15 | ### Patch Changes
16 |
17 | - Updated dependencies
18 | - @vite-plugin-vercel/vike@9.1.0
19 |
20 | ## 9.0.8
21 |
22 | ### Patch Changes
23 |
24 | - feat: adding support for node@24
25 | - Updated dependencies
26 | - @vite-plugin-vercel/vike@9.0.7
27 |
28 | ## 9.0.7
29 |
30 | ### Patch Changes
31 |
32 | - Updated dependencies
33 | - @vite-plugin-vercel/vike@9.0.6
34 |
35 | ## 9.0.6
36 |
37 | ### Patch Changes
38 |
39 | - 3f3dd9c: fix(vike): do not show route function warnings for non route function
40 | - 3f3dd9c: fix(vike): streaming error in \_error pages
41 | - Updated dependencies
42 | - Updated dependencies [3f3dd9c]
43 | - Updated dependencies [3f3dd9c]
44 | - @vite-plugin-vercel/vike@9.0.5
45 |
46 | ## 9.0.5
47 |
48 | ### Patch Changes
49 |
50 | - Updated dependencies
51 | - @vite-plugin-vercel/vike@9.0.4
52 |
53 | ## 9.0.4
54 |
55 | ### Patch Changes
56 |
57 | - Update peerDependencies to include Vite6
58 | - Updated dependencies
59 | - @vite-plugin-vercel/vike@9.0.3
60 |
61 | ## 9.0.3
62 |
63 | ### Patch Changes
64 |
65 | - fix: pass endpoint buildOptions to esbuild
66 |
67 | ## 9.0.2
68 |
69 | ### Patch Changes
70 |
71 | - Upgrade dependencies
72 | - fix CVE-2024-45296. See https://github.com/advisories/GHSA-9wv6-86v2-598j
73 | - Updated dependencies
74 | - Updated dependencies
75 | - @vite-plugin-vercel/vike@9.0.2
76 |
77 | ## 9.0.1
78 |
79 | ### Patch Changes
80 |
81 | - feat: Vike now support headers in +config.ts
82 | - Updated dependencies
83 | - @vite-plugin-vercel/vike@9.0.1
84 |
85 | ## 9.0.0
86 |
87 | ### Minor Changes
88 |
89 | - feat(vike): support edge config in +config.ts file
90 |
91 | ### Patch Changes
92 |
93 | - Updated dependencies
94 | - @vite-plugin-vercel/vike@9.0.0
95 |
96 | ## 8.0.1
97 |
98 | ### Patch Changes
99 |
100 | - avoid double build of additionnal endpoint
101 | - Updated dependencies
102 | - @vite-plugin-vercel/vike@8.0.1
103 |
104 | ## 8.0.0
105 |
106 | ### Minor Changes
107 |
108 | - Target node@18 by default
109 |
110 | ### Patch Changes
111 |
112 | - Updated dependencies
113 | - @vite-plugin-vercel/vike@8.0.0
114 |
115 | ## 7.0.2
116 |
117 | ### Patch Changes
118 |
119 | - Explicitely export types
120 | - @vite-plugin-vercel/vike@7.0.1
121 |
122 | ## 7.0.1
123 |
124 | ### Patch Changes
125 |
126 | - Better support for servers like express
127 | - Updated dependencies
128 | - @vite-plugin-vercel/vike@7.0.1
129 |
130 | ## 7.0.0
131 |
132 | ### Patch Changes
133 |
134 | - Updated dependencies
135 | - @vite-plugin-vercel/vike@7.0.0
136 |
137 | ## 6.0.1
138 |
139 | ### Patch Changes
140 |
141 | - Update target to es2022
142 | - Updated dependencies
143 | - @vite-plugin-vercel/vike@6.0.1
144 |
145 | ## 6.0.0
146 |
147 | ### Patch Changes
148 |
149 | - add support for supportsResponseStreaming
150 | - Updated dependencies
151 | - @vite-plugin-vercel/vike@6.0.0
152 |
153 | ## 5.0.5
154 |
155 | ### Patch Changes
156 |
157 | - Fix warning when trying to read exports
158 | - Updated dependencies
159 | - @vite-plugin-vercel/vike@5.0.3
160 |
161 | ## 5.0.4
162 |
163 | ### Patch Changes
164 |
165 | - Replace workspace-root by @manypkg/find-root
166 | - @vite-plugin-vercel/vike@5.0.2
167 |
168 | ## 5.0.3
169 |
170 | ### Patch Changes
171 |
172 | - Avoid potential url and path module conflict
173 | - @vite-plugin-vercel/vike@5.0.2
174 |
175 | ## 5.0.2
176 |
177 | ### Patch Changes
178 |
179 | - Updated dependencies
180 | - @vite-plugin-vercel/vike@5.0.2
181 |
182 | ## 5.0.1
183 |
184 | ### Patch Changes
185 |
186 | - Updated dependencies
187 | - @vite-plugin-vercel/vike@5.0.1
188 |
189 | ## 5.0.0
190 |
191 | ### Major Changes
192 |
193 | - Always target ESM by default
194 |
195 | ### Patch Changes
196 |
197 | - @vite-plugin-vercel/vike@5.0.0
198 |
199 | ## 4.0.2
200 |
201 | ### Patch Changes
202 |
203 | - Upgrade dependencies
204 | - Updated dependencies
205 | - @vite-plugin-vercel/vike@4.0.2
206 |
207 | ## 4.0.1
208 |
209 | ### Patch Changes
210 |
211 | - Fix cleanup order
212 | - Updated dependencies
213 | - @vite-plugin-vercel/vike@4.0.1
214 |
215 | ## 4.0.0
216 |
217 | ### Minor Changes
218 |
219 | - Handle API endpoints with brackets
220 |
221 | ### Patch Changes
222 |
223 | - @vite-plugin-vercel/vike@4.0.0
224 |
225 | ## 3.0.2
226 |
227 | ### Patch Changes
228 |
229 | - Add support for static vite builds
230 | - @vite-plugin-vercel/vike@3.0.1
231 |
232 | ## 3.0.1
233 |
234 | ### Patch Changes
235 |
236 | - 8e7cad4: Add vite@5 in peerDependencies
237 | - Updated dependencies [8e7cad4]
238 | - @vite-plugin-vercel/vike@3.0.1
239 |
240 | ## 3.0.0
241 |
242 | ### Major Changes
243 |
244 | - ec8dcba: - Target node20.x
245 | - Upgrade all dependencies to their latest versions
246 | - Drops support for vike non-v1 design (still works with v2)
247 |
248 | ### Patch Changes
249 |
250 | - Updated dependencies [ec8dcba]
251 | - @vite-plugin-vercel/vike@3.0.0
252 |
253 | ## 2.0.1
254 |
255 | ### Patch Changes
256 |
257 | - fix: warn instead of crash in case parsing fails
258 | - @vite-plugin-vercel/vike@2.0.0
259 |
260 | ## 2.0.0
261 |
262 | ### Minor Changes
263 |
264 | - Respect configured node version
265 |
266 | ### Patch Changes
267 |
268 | - @vite-plugin-vercel/vike@2.0.0
269 |
270 | ## 1.0.0
271 |
272 | ### Minor Changes
273 |
274 | - Ability to exports configs directly from endpoints
275 |
276 | ### Patch Changes
277 |
278 | - @vite-plugin-vercel/vike@1.0.0
279 |
280 | ## 0.3.7
281 |
282 | ### Patch Changes
283 |
284 | - Upgrade dependencies
285 | - Updated dependencies
286 | - @vite-plugin-vercel/vike@0.4.2
287 |
288 | ## 0.3.6
289 |
290 | ### Patch Changes
291 |
292 | - chore: remove esbuild verbose warnings
293 | - Updated dependencies
294 | - @vite-plugin-vercel/vike@0.4.1
295 |
296 | ## 0.3.5
297 |
298 | ### Patch Changes
299 |
300 | - fix: ensure output folder is created
301 | - @vite-plugin-vercel/vike@0.4.0
302 |
303 | ## 0.3.4
304 |
305 | ### Patch Changes
306 |
307 | - Rename vite-plugin-vercel to Vike
308 | - Updated dependencies
309 | - @vite-plugin-vercel/vike@0.4.0
310 |
311 | ## 0.3.3
312 |
313 | ### Patch Changes
314 |
315 | - Adds support for @vercel/og
316 | - @vite-plugin-vercel/vike@0.3.3
317 |
318 | ## 0.3.2
319 |
320 | ### Patch Changes
321 |
322 | - Add support for vike V1 design
323 | - Updated dependencies
324 | - @vite-plugin-vercel/vike@0.3.3
325 |
326 | ## 0.3.1
327 |
328 | ### Patch Changes
329 |
330 | - update dependencies
331 | - Updated dependencies
332 | - @vite-plugin-vercel/vike@0.3.2
333 |
334 | ## 0.3.0
335 |
336 | ### Minor Changes
337 |
338 | - Rename Vike integration plugin to @vite-plugin-vercel/vike
339 |
340 | ### Patch Changes
341 |
342 | - Updated dependencies
343 | - @vite-plugin-vercel/vike@0.3.1
344 |
345 | ## 0.2.2
346 |
347 | ### Patch Changes
348 |
349 | - Update documentation and upgrade dependencies
350 |
351 | ## 0.2.1
352 |
353 | ### Patch Changes
354 |
355 | - fix use case without vike
356 |
357 | ## 0.2.0
358 |
359 | ### Minor Changes
360 |
361 | - upgrade dependencies
362 |
363 | ### Patch Changes
364 |
365 | - support edge-light target
366 |
367 | ## 0.1.7
368 |
369 | ### Patch Changes
370 |
371 | - fix issue with win32 path
372 |
373 | ## 0.1.6
374 |
375 | ### Patch Changes
376 |
377 | - Upgrade dependencies. Fixes #9 and #10
378 |
379 | ## 0.1.5
380 |
381 | ### Patch Changes
382 |
383 | - Edge Functions support
384 |
385 | ## 0.1.4
386 |
387 | ### Patch Changes
388 |
389 | - Support for vite@3
390 |
391 | ## 0.1.3
392 |
393 | ### Patch Changes
394 |
395 | - Support rewrites/redirects options (prefered over config.routes)
396 |
397 | ## 0.1.2
398 |
399 | ### Patch Changes
400 |
401 | - Truncate user provided static html files
402 |
403 | ## 0.1.1
404 |
405 | ### Patch Changes
406 |
407 | - Create a dedicated package for vike integration
408 |
409 | ## 0.1.0
410 |
411 | ### Minor Changes
412 |
413 | - Vercel API v3 beta release with ISR support fix
414 |
415 | ## 0.0.7
416 |
417 | ### Patch Changes
418 |
419 | - remove references to API v2
420 |
421 | ## 0.0.6
422 |
423 | ### Patch Changes
424 |
425 | - change target of serverless function back to cjs
426 |
427 | ## 0.0.5
428 |
429 | ### Patch Changes
430 |
431 | - initial beta release for v3 Vercel API
432 |
--------------------------------------------------------------------------------
/packages/vercel/README.md:
--------------------------------------------------------------------------------
1 | # vite-plugin-vercel
2 |
3 | Vercel adapter for [Vite](https://vitejs.dev/).
4 |
5 | Bundle your Vite application as supported by [Vercel Output API (v3)](https://vercel.com/docs/build-output-api/v3).
6 |
7 | ## Install
8 |
9 | ```bash
10 | npm i -D vite-plugin-vercel
11 | ```
12 |
13 | ```bash
14 | yarn add -D vite-plugin-vercel
15 | ```
16 |
17 | ```bash
18 | pnpm add -D vite-plugin-vercel
19 | ```
20 |
21 | ```bash
22 | bun add -D vite-plugin-vercel
23 | ```
24 |
25 | ## Features
26 |
27 | - [x] [SSG/Static files](https://vercel.com/docs/build-output-api/v3/primitives#static-files)
28 | - see [`prerender` config](/packages/vercel/src/types.ts#L37)
29 | - [x] [SSR/Serverless functions](https://vercel.com/docs/build-output-api/v3/primitives#serverless-functions)
30 | - `.[jt]s` files under the `/api` folder of your project are automatically bundled as Serverless functions under `.vercel/output/functions/api/*.func`
31 | - see [`additionalEndpoints` config](/packages/vercel/src/types.ts#L62)
32 | - [x] [ISR/Prerender functions](https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions)
33 | - see [`isr` config](/packages/vercel/src/types.ts#L89). Also see implementation of [vike](/packages/vike-integration/vike.ts) for example
34 | - [x] [Edge functions](https://vercel.com/docs/build-output-api/v3/primitives#edge-functions)
35 | - [x] [Edge middleware](https://vercel.com/docs/functions/edge-middleware/middleware-api)
36 | - [ ] [Images optimization](https://vercel.com/docs/build-output-api/v3/configuration#images)
37 | - [ ] [Preview mode](https://vercel.com/docs/build-output-api/v3/features#preview-mode)
38 | - [x] [Advanced config](/packages/vercel/src/types.ts#L19)
39 |
40 | ## Simple usage
41 |
42 | Install this package as a dev dependency and add it to your Vite config:
43 |
44 | ```ts
45 | // vite.config.ts
46 | import { defineConfig } from 'vite';
47 | import vercel from 'vite-plugin-vercel';
48 |
49 | export default defineConfig({
50 | plugins: [vercel()],
51 | vercel: {
52 | // optional configuration options, see "Advanced usage" below for details
53 | },
54 | });
55 | ```
56 |
57 | > [!NOTE]
58 | > Files under `/api` or `/_api` directory will automatically be added under `/api/*` route
59 | > Prefer using `/_api` directory, as `@vercel/build` is currently force building `/api` files,
60 | > with no way to disable it, thus avoiding double compilation and unexpected behaviour.
61 |
62 | ### Configure endpoints
63 |
64 | Endpoints under `/api`, `/_api` or added through `additionalEndpoints` can be configured
65 | by exporting values from the endpoint file:
66 |
67 | ```ts
68 | // file: _api/endpoint.ts
69 |
70 | // Should run on edge runtime
71 | export const edge = true;
72 |
73 | // Always add those header to this endpoint
74 | export const headers = {
75 | 'Some-Header': 'some value',
76 | };
77 |
78 | // Stream the response
79 | export const streaming = true;
80 |
81 | // Enable Incremental Static Regeneration for this endpoint
82 | export const isr = {
83 | expiration: 30,
84 | };
85 |
86 | export default async function handler() {
87 | return new Response('Edge Function: OK', {
88 | status: 200,
89 | });
90 | }
91 | ```
92 |
93 | > [!NOTE]
94 | > Please create an issue if you need other per-endpoints configurations
95 |
96 | ### Edge middleware
97 |
98 | You can use [Edge middleware as describe in the official documentation](https://vercel.com/docs/functions/edge-middleware/middleware-api) (i.e. with a `middleware.ts` file at the root of your project).
99 |
100 | ## Usage with Vike
101 |
102 | [Vike](https://vike.dev/) is supported through [@vite-plugin-vercel/vike](/packages/vike-integration/README.md) plugin.
103 |
104 | You only need to install `@vite-plugin-vercel/vike`, the Vite config stays the same as above.
105 |
106 | You can then leverage [config files](https://vike.dev/config) to customize your endpoints:
107 |
108 | ```ts
109 | // /pages/product/+config.ts
110 |
111 | import Page from './Page';
112 | import type { Config } from 'vike/types';
113 |
114 | export default {
115 | // Customize ISR config for this page
116 | isr: { expiration: 15 },
117 | // Target Edge instead of Serverless
118 | edge: true,
119 | // append headers to all responses
120 | headers: {
121 | 'X-Header': 'value'
122 | }
123 | } satisfies Config;
124 | ```
125 |
126 | You will also need to extend the [renderer config](https://vike.dev/config#renderer) so that `vike` is aware of the new parameter:
127 |
128 | ```ts
129 | // /renderer/+config.ts
130 |
131 | import config from '@vite-plugin-vercel/vike/config';
132 | import type { Config } from 'vike/types';
133 |
134 | export default {
135 | extends: config,
136 | } satisfies Config;
137 | ```
138 |
139 | ## Advanced usage
140 |
141 | ```ts
142 | // vite.config.ts
143 | import { defineConfig } from 'vite';
144 | import vercel from 'vite-plugin-vercel';
145 |
146 | export default defineConfig({
147 | plugins: [vercel()],
148 | vercel: {
149 | // All the followings optional
150 |
151 | /**
152 | * How long Functions should be allowed to run for every request, in seconds.
153 | * If left empty, default value for your plan will be used.
154 | */
155 | defaultMaxDuration: 30,
156 | /**
157 | * Enable streaming responses by default for all Serverless Functions
158 | */
159 | defaultSupportsResponseStreaming: true,
160 | /**
161 | * Default expiration time (in seconds) for prerender functions.
162 | * Defaults to 86400 seconds (24h).
163 | */
164 | expiration: 86400,
165 | /**
166 | * Also known as Server Side Generation, or SSG.
167 | * If present, this function is responsible to create static files in `.vercel/output/static`.
168 | * Defaults to `false`, which disables prerendering.
169 | */
170 | prerender(resolvedConfig) {
171 | // Check `/packages/vike/vike.ts` `prerender` for an example
172 | },
173 | /**
174 | * See https://vercel.com/docs/projects/project-configuration#rewrites
175 | */
176 | rewrites: [{ source: '/about', destination: '/about-our-company.html' }],
177 | /**
178 | * @see {@link https://vercel.com/docs/projects/project-configuration#headers}
179 | */
180 | headers: [
181 | {
182 | "source": "/service-worker.js",
183 | "headers": [
184 | {
185 | "key": "Cache-Control",
186 | "value": "public, max-age=0, must-revalidate"
187 | }
188 | ]
189 | }
190 | ],
191 | /**
192 | * See https://vercel.com/docs/projects/project-configuration#redirects
193 | */
194 | redirects: [
195 | { source: '/me', destination: '/profile.html', permanent: false },
196 | ],
197 | /**
198 | * See https://vercel.com/docs/projects/project-configuration#cleanurls
199 | */
200 | cleanUrls: true,
201 | /**
202 | * See https://vercel.com/docs/projects/project-configuration#trailingslash
203 | */
204 | trailingSlash: true,
205 | /**
206 | * By default, all `api/*` and `_api/*` endpoints are compiled under `.vercel/output/functions/api/*.func`.
207 | * If others serverless functions need to be compiled under `.vercel/output/functions`, they should be added here.
208 | * For instance, a framework can leverage this to have a generic ssr endpoint
209 | * without requiring the user to write any code.
210 | */
211 | additionalEndpoints: [
212 | {
213 | // can also be an Object representing an esbuild StdinOptions
214 | source: '/path/to/file.ts',
215 | // URL path of the handler, will be generated to `.vercel/output/functions/api/file.func/index.js`
216 | destination: '/api/file',
217 | },
218 | ],
219 | /**
220 | * Advanced configuration to override .vercel/output/config.json
221 | * See https://vercel.com/docs/build-output-api/v3/configuration#configuration
222 | */
223 | config: {
224 | // routes?: Route[];
225 | // images?: ImagesConfig;
226 | // wildcard?: WildcardConfig;
227 | // overrides?: OverrideConfig;
228 | // cache?: string[];
229 | // crons?: CronsConfig;
230 | },
231 | /**
232 | * ISR and SSG pages are mutually exclusive. If a page is found in both, ISR prevails.
233 | * Keys are path relative to .vercel/output/functions directory, either without extension,
234 | * or with `.prerender-config.json` extension.
235 | * If you have multiple isr configurations pointing to the same underlying function, you can leverage the `symlink`
236 | * property.
237 | *
238 | * Can be an object or a function returning an object (or a Promise of an object).
239 | *
240 | * Check `/packages/vike/vike.ts` `vitePluginVercelVpsIsrPlugin` for advanced usage.
241 | */
242 | isr: {
243 | // `symlink: 'ssr_'` means that a function is available under `.vercel/output/functions/ssr_.func`
244 | '/pages/a': { expiration: 15, symlink: 'ssr_', route: '^/a/.*$' },
245 | '/pages/b/c': { expiration: 15, symlink: 'ssr_', route: '^/b/c/.*$' },
246 | '/pages/d': { expiration: 15, symlink: 'ssr_', route: '^/d$' },
247 | '/pages/e': { expiration: 25 },
248 | },
249 | /**
250 | * Defaults to `.vercel/output`. Mostly useful for testing purpose
251 | */
252 | outDir: '.vercel/output',
253 | },
254 | });
255 | ```
256 |
257 | ## Demo
258 |
259 | https://vike-photon-demo.vercel.app/
260 |
--------------------------------------------------------------------------------
/packages/vercel/src/build.ts:
--------------------------------------------------------------------------------
1 | import fs, { copyFile } from "node:fs/promises";
2 | import { builtinModules } from "node:module";
3 | import path, { basename } from "node:path";
4 | import { findRoot } from "@manypkg/find-root";
5 | import { getNodeVersion } from "@vercel/build-utils";
6 | import { nodeFileTrace } from "@vercel/nft";
7 | import type { Header, Rewrite } from "@vercel/routing-utils";
8 | import { type BuildOptions, type Plugin, build } from "esbuild";
9 | import glob from "fast-glob";
10 | import { type ASTNode, generateCode, loadFile } from "magicast";
11 | import type { ResolvedConfig } from "vite";
12 | import { assert } from "./assert";
13 | import { vercelOutputVcConfigSchema } from "./schemas/config/vc-config";
14 | import { vercelEndpointExports } from "./schemas/exports";
15 | import type { VercelOutputIsr, ViteVercelApiEntry } from "./types";
16 | import { getOutput, getRoot, pathRelativeTo } from "./utils";
17 |
18 | export async function getAdditionalEndpoints(resolvedConfig: ResolvedConfig) {
19 | const userEndpoints: ViteVercelApiEntry[] = [];
20 | if (Array.isArray(resolvedConfig.vercel?.additionalEndpoints)) {
21 | for (const endpoint of resolvedConfig.vercel.additionalEndpoints) {
22 | if (typeof endpoint === "function") {
23 | const res = await endpoint();
24 | if (Array.isArray(res)) {
25 | userEndpoints.push(...res);
26 | } else {
27 | userEndpoints.push(res);
28 | }
29 | } else {
30 | userEndpoints.push(endpoint);
31 | }
32 | }
33 | }
34 |
35 | return userEndpoints.map((e) => ({
36 | ...e,
37 | route: e.route ?? true,
38 | // path.resolve removes the trailing slash if any
39 | destination: `${path.posix.resolve("/", e.destination)}.func`,
40 | }));
41 | }
42 |
43 | export async function getEntries(resolvedConfig: ResolvedConfig): Promise {
44 | const apiEntries = glob
45 | .sync(`${getRoot(resolvedConfig)}/api/**/*.*([a-zA-Z0-9])`)
46 | // from Vercel doc: Files with the underscore prefix are not turned into Serverless Functions.
47 | .filter((filepath) => !path.basename(filepath).startsWith("_"));
48 |
49 | if (apiEntries.length > 0) {
50 | console.warn(
51 | "@vercel/build is currently force building /api files itself, with no way to disable it. " +
52 | "In order to avoid double compilation, you should temporarily rename /api to /_api while using this plugin. " +
53 | "/_api functions are compiled under .vercel/output/functions/api/*.func as if they were in /api.",
54 | );
55 | }
56 |
57 | const otherApiEntries = glob
58 | .sync(`${getRoot(resolvedConfig)}/_api/**/*.*([a-zA-Z0-9])`)
59 | // from Vercel doc: Files with the underscore prefix are not turned into Serverless Functions.
60 | .filter((filepath) => !path.basename(filepath).startsWith("_"));
61 |
62 | return [...apiEntries, ...otherApiEntries].reduce(
63 | (entryPoints, filePath) => {
64 | const outFilePath = pathRelativeTo(filePath, resolvedConfig, filePath.includes("/_api/") ? "_api" : "api");
65 | const parsed = path.posix.parse(outFilePath);
66 |
67 | entryPoints.push({
68 | source: filePath,
69 | destination: `api/${path.posix.join(parsed.dir, parsed.name)}.func`,
70 | route: true,
71 | });
72 |
73 | return entryPoints;
74 | },
75 | await getAdditionalEndpoints(resolvedConfig),
76 | );
77 | }
78 |
79 | const edgeWasmPlugin: Plugin = {
80 | name: "edge-wasm-vercel",
81 | setup(build) {
82 | build.onResolve({ filter: /\.wasm/ }, (args) => {
83 | return {
84 | path: args.path.replace(/\.wasm\?module$/i, ".wasm"),
85 | external: true,
86 | };
87 | });
88 | },
89 | };
90 |
91 | const vercelOgPlugin = (ctx: { found: boolean; index: string }): Plugin => {
92 | return {
93 | name: "vercel-og",
94 | setup(build) {
95 | build.onResolve({ filter: /@vercel\/og/ }, () => {
96 | ctx.found = true;
97 | return undefined;
98 | });
99 |
100 | build.onLoad({ filter: /@vercel\/og/ }, (args) => {
101 | ctx.index = args.path;
102 | return undefined;
103 | });
104 | },
105 | };
106 | };
107 |
108 | const standardBuildOptions: BuildOptions = {
109 | bundle: true,
110 | target: "es2022",
111 | format: "esm",
112 | platform: "node",
113 | logLevel: "info",
114 | logOverride: {
115 | "ignored-bare-import": "verbose",
116 | "require-resolve-not-external": "verbose",
117 | },
118 | minify: false,
119 | plugins: [],
120 | define: {
121 | "process.env.NODE_ENV": '"production"',
122 | "import.meta.env.NODE_ENV": '"production"',
123 | },
124 | };
125 |
126 | export async function buildFn(resolvedConfig: ResolvedConfig, entry: ViteVercelApiEntry, buildOptions?: BuildOptions) {
127 | assert(
128 | entry.destination.length > 0,
129 | `Endpoint ${typeof entry.source === "string" ? entry.source : "-"} does not have build destination`,
130 | );
131 |
132 | const options = Object.assign({}, standardBuildOptions);
133 |
134 | if (buildOptions) {
135 | Object.assign(options, buildOptions);
136 | }
137 |
138 | const filename = entry.edge || options.format === "cjs" ? "index.js" : "index.mjs";
139 | const outfile = path.join(getOutput(resolvedConfig, "functions"), entry.destination, filename);
140 |
141 | Object.assign(options, { outfile });
142 |
143 | if (!options.stdin) {
144 | if (typeof entry.source === "string") {
145 | options.entryPoints = [entry.source];
146 | } else {
147 | assert(typeof entry.source === "object", "`{ source }` must be a string or an object");
148 | assert(typeof entry.source.contents === "string", "`{ contents }` must be a string");
149 | options.stdin = entry.source;
150 | }
151 | }
152 |
153 | if (entry.edge) {
154 | options.platform = undefined;
155 | options.external = [...builtinModules, ...builtinModules.map((m) => `node:${m}`)];
156 | options.conditions = ["edge-light", "worker", "browser", "module", "import", "require"];
157 | options.plugins?.push(edgeWasmPlugin);
158 | options.format = "esm";
159 | } else if (options.format === "esm") {
160 | options.banner = {
161 | js: `import { createRequire as VPV_createRequire } from "node:module";
162 | import { fileURLToPath as VPV_fileURLToPath } from "node:url";
163 | import { dirname as VPV_dirname } from "node:path";
164 | const require = VPV_createRequire(import.meta.url);
165 | const __filename = VPV_fileURLToPath(import.meta.url);
166 | const __dirname = VPV_dirname(__filename);
167 | `,
168 | };
169 | }
170 |
171 | const ctx = { found: false, index: "" };
172 | options.plugins?.push(vercelOgPlugin(ctx));
173 |
174 | const output = await build(options);
175 |
176 | // guess some assets dependencies
177 | if (typeof entry.source === "string") {
178 | let base = resolvedConfig.root;
179 | try {
180 | const dir = await findRoot(resolvedConfig.root);
181 | base = dir.rootDir;
182 | } catch (e) {
183 | // ignore error
184 | }
185 | const { fileList, reasons } = await nodeFileTrace([entry.source], {
186 | base,
187 | processCwd: resolvedConfig.root,
188 | mixedModules: true,
189 | ignore: [
190 | "**/node_modules/react{,-dom,-dom-server-turbopack}/**/*.development.js",
191 | "**/*.d.ts",
192 | "**/*.map",
193 | "**/node_modules/webpack5/**/*",
194 | ],
195 | async readFile(filepath) {
196 | if (filepath.endsWith(".ts") || filepath.endsWith(".tsx")) {
197 | const result = await build({
198 | ...standardBuildOptions,
199 | entryPoints: [entry.source as string],
200 | bundle: false,
201 | write: false,
202 | });
203 |
204 | return result.outputFiles[0].text;
205 | }
206 |
207 | return fs.readFile(filepath, "utf-8");
208 | },
209 | });
210 |
211 | for (const file of fileList) {
212 | if (
213 | reasons.has(file) &&
214 | reasons.get(file)?.type.includes("asset") &&
215 | !file.endsWith(".js") &&
216 | !file.endsWith(".cjs") &&
217 | !file.endsWith(".mjs") &&
218 | !file.endsWith("package.json")
219 | ) {
220 | await copyFile(
221 | path.join(base, file),
222 | path.join(getOutput(resolvedConfig, "functions"), entry.destination, basename(file)),
223 | );
224 | }
225 | }
226 | }
227 |
228 | await writeVcConfig(resolvedConfig, entry.destination, filename, {
229 | edge: Boolean(entry.edge),
230 | streaming: entry.streaming,
231 | });
232 |
233 | return output;
234 | }
235 |
236 | export async function writeVcConfig(
237 | resolvedConfig: ResolvedConfig,
238 | destination: string,
239 | filename: string,
240 | options: {
241 | edge: boolean;
242 | streaming?: boolean;
243 | },
244 | ): Promise {
245 | const vcConfig = path.join(getOutput(resolvedConfig, "functions"), destination, ".vc-config.json");
246 |
247 | const nodeVersion = await getNodeVersion(getOutput(resolvedConfig));
248 |
249 | await fs.writeFile(
250 | vcConfig,
251 | JSON.stringify(
252 | vercelOutputVcConfigSchema.parse(
253 | options.edge
254 | ? {
255 | runtime: "edge",
256 | entrypoint: filename,
257 | }
258 | : {
259 | runtime: nodeVersion.runtime,
260 | handler: filename,
261 | maxDuration: resolvedConfig.vercel?.defaultMaxDuration,
262 | launcherType: "Nodejs",
263 | shouldAddHelpers: true,
264 | supportsResponseStreaming: options.streaming ?? resolvedConfig.vercel?.defaultSupportsResponseStreaming,
265 | },
266 | ),
267 | undefined,
268 | 2,
269 | ),
270 | "utf-8",
271 | );
272 | }
273 |
274 | function getSourceAndDestination(destination: string) {
275 | if (destination.startsWith("api/")) {
276 | return path.posix.resolve("/", destination);
277 | }
278 | return path.posix.resolve("/", destination, ":match*");
279 | }
280 |
281 | const RE_BRACKETS = /^\[([^/]+)\]$/gm;
282 | function replaceBrackets(source: string) {
283 | return source
284 | .split("/")
285 | .map((segment) => segment.replace(RE_BRACKETS, ":$1"))
286 | .join("/");
287 | }
288 |
289 | function isPrimitive(test: unknown) {
290 | return test !== Object(test);
291 | }
292 |
293 | export function _eval(code: unknown): boolean {
294 | const func = new Function(`{ return function(){ return ${code} } };`);
295 | return func.call(null).call(null);
296 | }
297 |
298 | function evalExport(exp: unknown) {
299 | if (!exp) return;
300 |
301 | const code = isPrimitive(exp) ? exp : generateCode(exp as ASTNode).code;
302 |
303 | return _eval(code);
304 | }
305 |
306 | async function extractExports(filepath: string) {
307 | try {
308 | const mod = await loadFile(filepath);
309 |
310 | const subject = {
311 | edge: evalExport(mod.exports.edge),
312 | headers: evalExport(mod.exports.headers),
313 | streaming: evalExport(mod.exports.streaming),
314 | isr: evalExport(mod.exports.isr),
315 | };
316 |
317 | return vercelEndpointExports.parse(subject);
318 | } catch (e) {
319 | console.warn(`Warning: failed to read exports of '${filepath}'`, e);
320 | }
321 | }
322 |
323 | async function extractHeaders(resolvedConfig: ResolvedConfig) {
324 | let headers: Header[] = [];
325 | if (typeof resolvedConfig.vercel?.headers === "function") {
326 | headers = await resolvedConfig.vercel.headers();
327 | } else if (Array.isArray(resolvedConfig.vercel?.headers)) {
328 | headers = resolvedConfig.vercel.headers;
329 | }
330 |
331 | return headers;
332 | }
333 |
334 | export async function buildEndpoints(resolvedConfig: ResolvedConfig): Promise<{
335 | rewrites: Rewrite[];
336 | isr: Record;
337 | headers: Header[];
338 | }> {
339 | const entries = await getEntries(resolvedConfig);
340 | const headers = await extractHeaders(resolvedConfig);
341 |
342 | for (const entry of entries) {
343 | if (typeof entry.source === "string") {
344 | const exports = await extractExports(entry.source);
345 |
346 | if (exports) {
347 | if (entry.headers || exports.headers) {
348 | entry.headers = {
349 | ...exports.headers,
350 | ...entry.headers,
351 | };
352 | }
353 |
354 | if (entry.edge !== undefined && exports.edge !== undefined) {
355 | throw new Error(
356 | `edge configuration should be defined either in the endpoint itself or through Vite config, not both ('${entry.source}')`,
357 | );
358 | }
359 |
360 | if (exports.edge !== undefined) {
361 | entry.edge = exports.edge;
362 | }
363 |
364 | if (entry.isr !== undefined && exports.isr !== undefined) {
365 | throw new Error(
366 | `isr configuration should be defined either in the endpoint itself or through Vite config, not both ('${entry.source}')`,
367 | );
368 | }
369 |
370 | if (
371 | (entry.isr !== undefined || exports.isr !== undefined) &&
372 | (entry.edge !== undefined || exports.edge !== undefined)
373 | ) {
374 | throw new Error(`isr cannot be enabled for edge functions ('${entry.source}')`);
375 | }
376 |
377 | if (exports.isr) {
378 | entry.isr = exports.isr;
379 | }
380 |
381 | if (typeof exports.streaming === "boolean") {
382 | entry.streaming = exports.streaming;
383 | }
384 | }
385 | }
386 |
387 | await buildFn(resolvedConfig, entry, entry.buildOptions);
388 | }
389 |
390 | const isrEntries = entries
391 | .filter((e) => e.isr)
392 | .map((e) => [e.destination.replace(/\.func$/, ""), { expiration: e.isr?.expiration }] as const);
393 |
394 | return {
395 | rewrites: entries
396 | .filter((e) => {
397 | if (e.addRoute === undefined && e.route !== undefined) {
398 | return e.route !== false;
399 | }
400 | if (e.addRoute !== undefined && e.route === undefined) {
401 | return e.addRoute !== false;
402 | }
403 | if (e.addRoute !== undefined && e.route !== undefined) {
404 | throw new Error("Cannot use both `route` and `addRoute` in `additionalEndpoints`");
405 | }
406 | return true;
407 | })
408 | .map((e) => {
409 | const destination = e.destination.replace(/\.func$/, "");
410 | if (typeof e.route === "string") {
411 | return {
412 | source: `(${e.route})`,
413 | destination: `${destination}/?__original_path=$1`,
414 | };
415 | }
416 | return {
417 | source: replaceBrackets(getSourceAndDestination(destination)),
418 | destination: getSourceAndDestination(destination),
419 | };
420 | }),
421 | isr: Object.fromEntries(isrEntries) as Record,
422 | headers: [
423 | ...entries
424 | .filter((e) => e.headers)
425 | .map((e) => ({
426 | source: `/${e.destination.replace(/\.func$/, "")}`,
427 | headers: Object.entries(e.headers ?? {}).map(([key, value]) => ({
428 | key,
429 | value,
430 | })),
431 | })),
432 | ...headers,
433 | ],
434 | };
435 | }
436 |
--------------------------------------------------------------------------------