-
Notifications
You must be signed in to change notification settings - Fork 855
feat: add vercel deployment guide #7131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a new MDX guide documenting a programmatic Vercel + Prisma Postgres deployment workflow (CONFIG-based TypeScript orchestrator, six-step API sequence, retry/error helpers, and deployApp example) and updates Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer script
participant VercelAPI as Vercel REST API
participant PrismaStore as Prisma Postgres
participant VercelProject as Vercel Project/Deployment
rect rgba(0,128,96,0.06)
note right of Dev: deployApp() orchestrator (high-level)
end
Dev->>VercelAPI: POST /v10/projects (createProject)
VercelAPI-->>Dev: projectId
Dev->>VercelAPI: POST /v1/integrations/billing/authorization (createPrismaAuthorization)
VercelAPI-->>Dev: authorizationId
Dev->>VercelAPI: POST /v1/storage/stores/integration (createPrismaDatabase)
VercelAPI-->>PrismaStore: provision DB
VercelAPI-->>Dev: storeId + connectionString
Dev->>VercelAPI: POST /v1/integrations/installations/{configId}/connections (connectDatabaseToProject)
VercelAPI-->>Dev: connection confirmation
Dev->>VercelAPI: POST /v13/deployments (deployApplication)
VercelAPI-->>VercelProject: build & deploy
VercelAPI-->>Dev: deploymentUrl
Dev->>VercelAPI: POST /v9/projects/{projectId}/transfer-request (createProjectTransfer)
VercelAPI-->>Dev: transferCode / claimUrl
note right of Dev: deployApp() returns consolidated result (projectId, url, transfer info)
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests
Comment |
Dangerous URL checkNo absolute URLs to prisma.io/docs found. |
Redirect checkThis PR probably requires the following redirects to be added to static/_redirects:
|
Images automagically compressed by Calibre's image-actions ✨ Compression reduced images by 87.7%, saving 274.7 KB.
|
Deploying docs with
|
Latest commit: |
1915ac8
|
Status: | ✅ Deploy successful! |
Preview URL: | https://83cac4dd.docs-51g.pages.dev |
Branch Preview URL: | https://dc-5025-vercel-guide.docs-51g.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (7)
content/800-guides/380-vercel-app-deployment.mdx (6)
137-158
: Use the shared error/ retry helpers in all API calls.
createProject
(and later functions) don’t usehandleApiErrors
/apiCallWithRetry
, so failures will be silent and inconsistent with the guide’s best practices.- const response = await fetch( + const response = await apiCallWithRetry( `${CONFIG.VERCEL_API_URL}/v10/projects?teamId=${CONFIG.TEAM_ID}`, { method: "POST", headers: { Authorization: `Bearer ${CONFIG.ACCESS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: projectName }), } ); - const project = await response.json(); + await handleApiErrors(response, "Create project"); + const project = await response.json();
369-405
:fileSha
is undefined; make it a parameter and wire in error/retry.The orchestration example won’t run as-is. Accept
fileSha
(or upload first) and consistently use your helpers.-async function deployApp() { +async function deployApp(fileSha: string) { console.log("🚀 Starting instant deployment..."); @@ - const deployment = await deployApplication(project.name, fileSha); + const deployment = await deployApplication(project.name, fileSha); @@ return { projectId: project.id, deploymentUrl: `https://${deployment.url}`, claimCode: transfer.code, claimUrl: transfer.claimUrl, }; }
447-466
: Surface parsed JSON where possible and include response snippet in errors.Parsing only
text()
loses structure. Prefer JSON where applicable and include truncated payload for diagnostics.- const errorData = await response.text(); + const raw = await response.text(); + let errorData: unknown = raw; + try { errorData = JSON.parse(raw); } catch {} @@ - throw new Error(`${operation} failed: ${response.status} - ${errorData}`); + throw new Error(`${operation} failed: ${response.status} - ${typeof errorData === "string" ? errorData.slice(0, 500) : JSON.stringify(errorData).slice(0, 500)}`);
475-492
: Respect Retry-After, add jitter, and support timeouts.This improves resilience under rate limits and network stalls.
-async function apiCallWithRetry(url: string, options: RequestInit, maxRetries = 3) { +async function apiCallWithRetry(url: string, options: RequestInit, maxRetries = 3, timeoutMs = 15000) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - const response = await fetch(url, options); + const ac = new AbortController(); + const t = setTimeout(() => ac.abort(), timeoutMs); + const response = await fetch(url, { ...options, signal: ac.signal }); + clearTimeout(t); if (response.status === 429) { - const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff - await new Promise(resolve => setTimeout(resolve, waitTime)); + const ra = response.headers.get("retry-after"); + const base = ra ? Number(ra) * 1000 : Math.pow(2, attempt) * 1000; + const jitter = Math.floor(Math.random() * 250); + await new Promise(resolve => setTimeout(resolve, base + jitter)); continue; } return response; } catch (error) { if (attempt === maxRetries) throw error; } } }
499-506
: Add an explicit note to avoid logging or storing secrets (tokens and database URLs).Strengthen the guidance given the examples return sensitive values earlier.
- **Store tokens securely**: Never expose API tokens in client-side code + - **Never log or store secrets**: Avoid printing ACCESS_TOKEN, connection strings (DATABASE_URL), or other credentials to logs or analytics
121-129
: Avoid hard-coding product IDs — use Vercel's 'prisma' slug and VERCEL_TOKEN
- Replace PRISMA_PRODUCT_ID = "iap_yVdbiKqs5fLkYDAB" with the Marketplace slug "prisma" (use integrationProductIdOrSlug = "prisma").
- Rename ACCESS_TOKEN → VERCEL_TOKEN and store it as a Sensitive env var; optionally namespace other vars as VERCEL_TEAM_ID and VERCEL_INTEGRATION_CONFIG_ID.
File: content/800-guides/380-vercel-app-deployment.mdx lines 121-129.
sidebars.ts (1)
549-559
: Prefertype: "doc"
for internal pages over rawhref
for consistency and broken-link checks.Using
doc
ids enables Docusaurus validation and versioning. Keephref
only for external links.Example:
-{ - type: "link", - label: "Integrating the Vercel AI SDK in a Next.js application", - href: "/guides/ai-sdk-nextjs", -}, +{ + type: "doc", + id: "guides/ai-sdk-nextjs", + label: "Integrating the Vercel AI SDK in a Next.js application", +},Also applies to: 572-582, 595-600
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
static/img/guides/vercel_app_deployments.png
is excluded by!**/*.png
📒 Files selected for processing (2)
content/800-guides/380-vercel-app-deployment.mdx
(1 hunks)sidebars.ts
(4 hunks)
🔇 Additional comments (5)
sidebars.ts (2)
604-606
: LGTM on formatting tweaks.Trailing commas/comments here are harmless and match existing style.
453-454
: Ensure sidebar entry id matches MDX doc id/slugsidebars.ts references "guides/vercel-app-deployment" (sidebars.ts:453) but content/800-guides/380-vercel-app-deployment.mdx frontmatter has no
id
orslug
. Addslug: /guides/vercel-app-deployment
orid: guides/vercel-app-deployment
to the MDX frontmatter, or update the sidebar entry to the doc's actual id.content/800-guides/380-vercel-app-deployment.mdx (3)
33-33
: Validate external links and follow redirects.File: content/800-guides/380-vercel-app-deployment.mdx Lines: 33-33
[Try the demo](https://pris.ly/vercel_app_deployment_demo?utm_source=docs).
HEAD check results:
- https://pris.ly/vercel_app_deployment_demo?utm_source=docs — HTTP/2 302 (redirect)
- https://vercel.com/docs/deployments/claim-deployments — HTTP/2 200
- https://vercel.com/docs/rest-api — HTTP/2 302 (redirect)
- https://www.better-auth.com/ — HTTP/2 200
Action: follow the 302 redirects (e.g., curl -IL) and confirm the final URLs and any fragment anchors resolve to 200 and the intended content; update links/anchors or replace with final destination URLs if they will 404 at publish time.
Also applies to: 362-363, 407-407, 556-561
332-355
: Add API error handling; TTL is 24h — don't rely on returnUrl for expired/invalid codes.File: content/800-guides/380-vercel-app-deployment.mdx (lines 332-355)
- Replace the raw fetch with apiCallWithRetry and call handleApiErrors on the response; handle non-2xx results (show a user-facing error/fallback) instead of assuming the claim flow will redirect.
- Vercel docs: the POST returns a transfer
code
valid for 24 hours and the claim URL acceptsreturnUrl
; behavior for invalid/expired codes is not documented (the accept endpoint surfaces 4xx/422 errors), so explicitly handle these error cases in code. (vercel.com)
170-200
:metadata
should be an object, not a JSON string.Vercel's integrations/marketplace examples and responses use metadata as a JSON object; keep it as an object unless this exact endpoint explicitly requires a string. (vercel.com)
- metadata: JSON.stringify({ region: "iad1" }), + metadata: { region: "iad1" },
Co-authored-by: Petra Donka <donkapetra@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
content/800-guides/380-vercel-app-deployment.mdx (3)
388-421
: Production deployment: set target and prefer project ID.Include target: "production" for production aliases; consider using project.id for unambiguous targeting (v13 accepts a string).
Apply:
body: JSON.stringify({ files: [{ file: ".vercel/source.tgz", sha: fileSha }], name: `deployment-${Date.now()}`, projectSettings: { framework: "nextjs" }, - project: projectName, + project: projectName, // consider passing projectId instead + target: "production", }),Reference: “Create a new deployment” (target; project field). (vercel.com)
434-458
: URL‑encode returnUrl in the Claim URL.Avoid broken links if returnUrl contains query params.
Apply:
- const claimUrl = `https://vercel.com/claim-deployment?code=${transferData.code}&returnUrl=https://myapp.com/dashboard/projects`; + const returnUrl = "https://myapp.com/dashboard/projects"; + const claimUrl = `https://vercel.com/claim-deployment?code=${transferData.code}&returnUrl=${encodeURIComponent(returnUrl)}`;Also, note: Create transfer request endpoint is documented as POST /projects/{idOrName}/transfer-request. Your use of /v9/... works today, but aligning to the docs path keeps it future‑proof. (vercel.com)
498-549
: Use the provided helpers in examples for consistency.Wrap each fetch with apiCallWithRetry + handleApiErrors to model best practices in the guide (createProject, createPrismaAuthorization, etc.).
Example pattern:
- const response = await fetch(url, options); - const data = await response.json(); + const response = await apiCallWithRetry(url, options); + await handleApiErrors(response, "Create project"); + const data = await response.json();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
static/img/guides/vercel_complete_claim_demo.gif
is excluded by!**/*.gif
📒 Files selected for processing (1)
content/800-guides/380-vercel-app-deployment.mdx
(1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Documentation Checks
content/800-guides/380-vercel-app-deployment.mdx
[error] 79-79: cspell: Unknown word 'dafault'. Consider fixing to 'default'.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: runner / linkspector
- GitHub Check: Check internal links
- GitHub Check: Lost Pixel
🔇 Additional comments (5)
content/800-guides/380-vercel-app-deployment.mdx (5)
1-8
: Add a stable slug for routing and sidebar consistency.Without a slug, the sidebar link may 404. Add a frontmatter slug matching the sidebar entry.
Apply:
--- title: 'Instant app deployment with Vercel and Prisma Postgres' metaTitle: 'Instant app deployment with Vercel and Prisma Postgres' description: 'Learn how to programmatically deploy applications with Vercel and Prisma Postgres using the instant deployment API' sidebar_label: 'Vercel app deployment' image: '/img/guides/vercel_app_deployments.png' community_section: true +slug: /guides/vercel-app-deployment ---
343-347
: Good: connection string not surfaced.Returning only the store id avoids leaking sensitive credentials.
195-216
: Define how fileSha is produced (and upload the file).You call deployApplication(project.name, fileSha) but never show computing/uploading .vercel/source.tgz. Add a short “Upload files” subsection before Step 5 showing: compute SHA1 and POST /v2/files with x-vercel-digest, then reuse that SHA in files[].sha.
[ suggest_recommended_refactor ]
Reference: Upload Deployment Files and SHA guide. (vercel.com)Example to add (outside this block):
// Compute SHA1 of .vercel/source.tgz and upload const file = await fs.promises.readFile(".vercel/source.tgz"); const sha1 = createHash("sha1").update(file).digest("hex"); await fetch(`${CONFIG.VERCEL_API_URL}/v2/files?teamId=${CONFIG.TEAM_ID}`, { method: "POST", headers: { Authorization: `Bearer ${CONFIG.ACCESS_TOKEN}`, "Content-Length": String(file.byteLength), "x-vercel-digest": sha1, "Content-Type": "application/octet-stream", }, body: file, }); // Use sha1 as fileSha when calling deployApplication(...)
430-467
: Claim flow endpoints are correct; keep both options linked.POST /projects/{idOrName}/transfer-request and PUT /projects/transfer-request/{code} match current docs; the “Claim Deployments” URL workflow is accurately described (24h code).
If you want to strictly mirror docs’ latest paths, consider switching to the unversioned projects paths shown in the reference pages.
References: Create transfer request; Accept transfer; Claim Deployments. (vercel.com)
363-382
: Fix connect‑resource endpoint (remove products segment).Path should be installations/{integrationConfigurationId}/resources/{resourceId}/connections. Including /products/{productId} will 404.
Apply:
- `${CONFIG.VERCEL_API_URL}/v1/integrations/installations/${configId}/products/${CONFIG.PRISMA_PRODUCT_ID}/resources/${storeId}/connections?teamId=${CONFIG.TEAM_ID}`, + `${CONFIG.VERCEL_API_URL}/v1/integrations/installations/${configId}/resources/${storeId}/connections?teamId=${CONFIG.TEAM_ID}`,Reference: “Connect integration resource to project.” (vercel.com)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
content/800-guides/380-vercel-app-deployment.mdx (3)
104-106
: Soften/verify the credit‑card requirement statement.This can vary by account/plan and API usage; avoid absolute wording or add a citation.
-:::note Credit card requirement -Vercel requires a credit card to be attached to your account (even on the Hobby plan) to use the deployment APIs. Make sure to add payment information in your Vercel account settings before proceeding. +:::note Billing requirement +Some API operations may require a payment method on the Vercel account. If you encounter permission or billing errors, add a payment method in your Vercel account settings and retry. :::
441-454
: URL‑encode query parameters in the claim URL.Encode code and returnUrl to avoid breaking on special characters.
- const claimUrl = `https://vercel.com/claim-deployment?code=${transferData.code}&returnUrl=https://myapp.com/dashboard/projects`; + const claimUrl = `https://vercel.com/claim-deployment?code=${encodeURIComponent(transferData.code)}&returnUrl=${encodeURIComponent("https://myapp.com/dashboard/projects")}`;
534-550
: Consider Retry‑After and jitter in backoff.Use server-provided Retry‑After when present and add jitter.
- if (response.status === 429) { - const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff - await new Promise(resolve => setTimeout(resolve, waitTime)); + if (response.status === 429) { + const hdr = response.headers.get("retry-after"); + const base = hdr ? Number(hdr) * 1000 : Math.pow(2, attempt) * 1000; + const jitter = Math.floor(Math.random() * 250); + await new Promise(r => setTimeout(r, base + jitter)); continue; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
static/img/guides/vercel_complete_claim_demo_mini.gif
is excluded by!**/*.gif
📒 Files selected for processing (1)
content/800-guides/380-vercel-app-deployment.mdx
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Check internal links
- GitHub Check: runner / linkspector
- GitHub Check: Lost Pixel
- GitHub Check: Cloudflare Pages
🔇 Additional comments (8)
content/800-guides/380-vercel-app-deployment.mdx (8)
70-75
: Sync the “API endpoints overview” with current endpoints (Projects v11; connect-resource path).Update these two list items for accuracy and consistency with the code changes requested below.
-- `POST /v10/projects` - Create a new Vercel project +- `POST /v11/projects` - Create a new Vercel project - `POST /v1/integrations/billing/authorization` - Authorize Prisma integration - `POST /v1/storage/stores/integration` - Create Prisma Postgres database - `POST /v13/deployments` - Deploy application code -- `POST /v9/projects/{id}/transfer-request` - Generate claim code for user transfer +- `POST /v9/projects/{id}/transfer-request` - Generate claim code for user transfer +- `POST /v1/integrations/installations/{integrationConfigurationId}/resources/{resourceId}/connections` - Connect a database to a project
78-81
: Fix spelling/grammar and tighten the tip copy.Blocks the docs pipeline (cspell) and reads clearer.
-:::tip Contact us for elevated partner level access for db creation - -By default, every new partner is on our free plan which limited to 5 dbs per account, so if you are trying out this API and need higher db creation limits (which we suspect that most of you will), then please [contact us](https://www.prisma.io/partners#contact-us) to get partner level access. +:::tip Contact us for elevated partner-level access for DB creation + +By default, every new partner is on our free plan, which is limited to 5 DBs per account. If you need higher database creation limits while trying this API, please [contact us](https://www.prisma.io/partners#contact-us) to get partner-level access. :::
247-263
: Use Projects API v11 and apply consistent error handling.Switch to /v11 and run responses through handleApiErrors for clearer failures.
- const response = await fetch( - `${CONFIG.VERCEL_API_URL}/v10/projects?teamId=${CONFIG.TEAM_ID}`, + const response = await fetch( + `${CONFIG.VERCEL_API_URL}/v11/projects?teamId=${CONFIG.TEAM_ID}`, { method: "POST", headers: { Authorization: `Bearer ${CONFIG.ACCESS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: projectName }), } ); - - const project = await response.json(); + await handleApiErrors(response, "Create project"); + const project = await response.json();
320-351
: Good: connection string isn’t returned or logged.Thanks for omitting the raw connection string from return values/logs.
216-219
: Prefer projectId over name; add target:"production"; document file upload/sha.Use project ID for unambiguous behavior, set target for production aliases, and include an upload step for .vercel/source.tgz.
- const deployment = await deployApplication(project.name, fileSha); + const deployment = await deployApplication(project.id, fileSha);-async function deployApplication( - projectName: string, +async function deployApplication( + projectId: string, fileSha: string ): Promise<{ id: string; url: string }> { const response = await fetch( `${CONFIG.VERCEL_API_URL}/v13/deployments?teamId=${CONFIG.TEAM_ID}`, { method: "POST", headers: { Authorization: `Bearer ${CONFIG.ACCESS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ files: [{ file: ".vercel/source.tgz", sha: fileSha }], name: `deployment-${Date.now()}`, projectSettings: { framework: "nextjs" }, - project: projectName, + project: projectId, + target: "production", }), } );
- Add a short subsection before Step 5 to show uploading .vercel/source.tgz to POST /v2/files with x-vercel-digest (SHA‑1) and Content‑Length, and that files[].sha must match the uploaded digest.
Also applies to: 395-413
441-459
: Confirm Projects transfer endpoint version (v9) is still current.If Vercel has bumped versions for Projects endpoints, align this to current. Keep text and example in sync.
1-8
: Add a stable slug to prevent 404s from the sidebar link.The sidebar likely points to guides/vercel-app-deployment; add a matching slug in frontmatter.
--- title: 'Instant app deployment with Vercel and Prisma Postgres' metaTitle: 'Instant app deployment with Vercel and Prisma Postgres' description: 'Learn how to programmatically deploy applications with Vercel and Prisma Postgres using the instant deployment API' sidebar_label: 'Vercel app deployment' image: '/img/guides/vercel_app_deployments.png' community_section: true +slug: /guides/vercel-app-deployment ---
365-384
: Fix connect‑resource endpoint path (remove /products/{...}).Use Vercel’s connect‑resource endpoint: installations/{configId}/resources/{storeId}/connections with { projectId } in the body.
- await fetch( - `${CONFIG.VERCEL_API_URL}/v1/integrations/installations/${configId}/products/${CONFIG.PRISMA_PRODUCT_ID}/resources/${storeId}/connections?teamId=${CONFIG.TEAM_ID}`, + await fetch( + `${CONFIG.VERCEL_API_URL}/v1/integrations/installations/${configId}/resources/${storeId}/connections?teamId=${CONFIG.TEAM_ID}`, { method: "POST", headers: { Authorization: `Bearer ${CONFIG.ACCESS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ projectId }), } );
Summary by CodeRabbit