Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/lychee.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
with:
# prisma.io/extensions ships with PR #8232; drop this exclude once it is live.
# /compare/neon, /compare/planetscale, and /compare/supabase ship with PR #8516; drop that exclude once they are live.
# /compare/vercel-neon-drizzle is not live yet; drop that exclude once it is.
# dashboard.clerk.com/~/<page> resolves to the signed-in user's application and returns 404 to anonymous requests.
args: >-
--cache
Expand All @@ -58,7 +59,7 @@ jobs:
--exclude 'https://pris.ly/vercel-app-demo-live'
--exclude 'https://app-deploy-demo.prisma.io'
--exclude '^https://www\.prisma\.io/extensions'
--exclude '^https://www\.prisma\.io/compare/(neon|planetscale|supabase)$'
--exclude '^https://www\.prisma\.io/compare/(neon|planetscale|supabase|vercel-neon-drizzle)$'
--exclude '^https://dashboard\.clerk\.com/~/'
--accept 200,201,204,301,302,303,307,308,403,429
${{ steps.changed-docs.outputs.all_changed_files }}
Expand Down Expand Up @@ -94,7 +95,7 @@ jobs:
--exclude 'https://pris.ly/vercel-app-demo-live'
--exclude 'https://app-deploy-demo.prisma.io'
--exclude '^https://www\.prisma\.io/extensions'
--exclude '^https://www\.prisma\.io/compare/(neon|planetscale|supabase)$'
--exclude '^https://www\.prisma\.io/compare/(neon|planetscale|supabase|vercel-neon-drizzle)$'
--exclude '^https://dashboard\.clerk\.com/~/'
--accept 200,201,204,301,302,303,307,308,403,429
${{ steps.changed-docs.outputs.all_changed_files }}
Expand Down
1 change: 1 addition & 0 deletions apps/site/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ const agentMarkdownPaths = [
"/compare/neon",
"/compare/planetscale",
"/compare/supabase",
"/compare/vercel-neon-drizzle",
];

/**
Expand Down
95 changes: 67 additions & 28 deletions apps/site/src/app/compare/article.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const AI_MARKDOWN: Partial<Record<ComparisonSlug, string>> = {
neon: "/compare/neon.md",
planetscale: "/compare/planetscale.md",
supabase: "/compare/supabase.md",
"vercel-neon-drizzle": "/compare/vercel-neon-drizzle.md",
};

const TOC: Record<ComparisonSlug, readonly TocItem[]> = {
Expand Down Expand Up @@ -112,6 +113,18 @@ const TOC: Record<ComparisonSlug, readonly TocItem[]> = {
{ id: "faq", label: "FAQ", also: ["next"] },
{ id: "sources", label: "Sources" },
],
"vercel-neon-drizzle": [
{ id: "choose", label: "When to choose" },
{ id: "feature-table", label: "Features" },
{ id: "where-each", label: "Where each fits" },
{ id: "apart", label: "Why Prisma" },
{ id: "pricing", label: "Pricing" },
{ id: "wrong-choice", label: "When it is the wrong choice" },
{ id: "migrate", label: "Switching", also: ["migrate-back"] },
{ id: "proof", label: "What teams say" },
{ id: "faq", label: "FAQ", also: ["next"] },
{ id: "sources", label: "Sources" },
],
};

/**
Expand Down Expand Up @@ -168,28 +181,47 @@ export function comparisonChooseTable({
}

/** Buttons under the introduction. ORM pages lead with the ORM quickstart. */
export function comparisonHeroCtas(kind: "orm" | "postgres" = "postgres") {
export function comparisonHeroCtas(
kind: "orm" | "postgres" = "postgres",
options?: { secondary?: boolean; primaryLabel?: string },
) {
const primary = kind === "orm" ? ORM_HERO_CTAS.primary : POSTGRES_HERO_CTAS.primary;
const secondary = kind === "orm" ? ORM_HERO_CTAS.secondary : POSTGRES_HERO_CTAS.secondary;
const showSecondary = options?.secondary !== false;
return (
<p className="mt-8 flex flex-wrap items-center gap-3">
<PrismButton href={primary.href} ctaLocation="compare-intro">
{primary.label}
</PrismButton>{" "}
<PrismButtonOutline
href={secondary.href}
ctaLocation="compare-intro"
{...(secondary.href === CREATE_DB_WEB ? CREATE_DB_NEW_TAB : {})}
>
{secondary.label}
</PrismButtonOutline>
{options?.primaryLabel ?? primary.label}
</PrismButton>
{showSecondary ? (
<>
{" "}
<PrismButtonOutline
href={secondary.href}
ctaLocation="compare-intro"
{...(secondary.href === CREATE_DB_WEB ? CREATE_DB_NEW_TAB : {})}
>
{secondary.label}
</PrismButtonOutline>
</>
) : null}
</p>
);
}

/** Closing action. ORM pages keep the 500K+ Prisma ORM figure and ORM-specific buttons. */
export function comparisonClosingCta(kind: "orm" | "postgres" = "postgres") {
export function comparisonClosingCta(
kind: "orm" | "postgres" = "postgres",
options?: { instantDatabase?: boolean; secondary?: boolean; firstCheck?: string },
) {
const cta = kind === "orm" ? ORM_COMPARISON_CTA : COMPARISON_CTA;
const checks = options?.firstCheck
? cta.checks.map((item, index) =>
index === 0 ? { ...item, label: options.firstCheck as string } : item,
)
: cta.checks;
const showInstantDatabase = options?.instantDatabase !== false;
const showSecondary = options?.secondary !== false;
return (
<section
key="next"
Expand All @@ -207,29 +239,36 @@ export function comparisonClosingCta(kind: "orm" | "postgres" = "postgres") {
</p>
) : null}
<ul className="mt-4 max-w-[36rem] list-disc pl-5">
{cta.checks.map((item) => (
{checks.map((item) => (
<li key={item.label}>{item.label}</li>
))}
</ul>
<p className="mt-3 max-w-[36rem] text-sm leading-6 text-muted-foreground">
Need a database?{" "}
<a
href={CREATE_DB_WEB}
target={CREATE_DB_NEW_TAB.target}
rel={CREATE_DB_NEW_TAB.rel}
className="underline underline-offset-4 decoration-black/35 transition-colors hover:text-foreground hover:decoration-black/50"
>
Create an instant database
</a>
.
</p>
{showInstantDatabase ? (
<p className="mt-3 max-w-[36rem] text-sm leading-6 text-muted-foreground">
Need a database?{" "}
<a
href={CREATE_DB_WEB}
target={CREATE_DB_NEW_TAB.target}
rel={CREATE_DB_NEW_TAB.rel}
className="underline underline-offset-4 decoration-black/35 transition-colors hover:text-foreground hover:decoration-black/50"
>
Create an instant database
</a>
.
</p>
) : null}
<p className="mt-7 flex flex-wrap items-center gap-3">
<PrismButton href={cta.primaryCta.href} ctaLocation="compare-close">
{cta.primaryCta.label}
</PrismButton>{" "}
<PrismButtonOutline href={cta.secondaryCta.href} ctaLocation="compare-close">
{cta.secondaryCta.label}
</PrismButtonOutline>
</PrismButton>
{showSecondary ? (
<>
{" "}
<PrismButtonOutline href={cta.secondaryCta.href} ctaLocation="compare-close">
{cta.secondaryCta.label}
</PrismButtonOutline>
</>
) : null}
</p>
</section>
);
Expand Down
11 changes: 11 additions & 0 deletions apps/site/src/app/compare/comparisons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { MONGOOSE_DESCRIPTION, MONGOOSE_TITLE } from "./mongoose/_content";
import { NEON_DESCRIPTION, NEON_TITLE } from "./neon/_content";
import { PLANETSCALE_DESCRIPTION, PLANETSCALE_TITLE } from "./planetscale/_content";
import { SUPABASE_DESCRIPTION, SUPABASE_TITLE } from "./supabase/_content";
import {
VERCEL_NEON_DRIZZLE_DESCRIPTION,
VERCEL_NEON_DRIZZLE_TITLE,
} from "./vercel-neon-drizzle/_content";

export const COMPARISON_INDEX = {
path: "/compare",
Expand Down Expand Up @@ -84,6 +88,13 @@ export const COMPARISONS = [
title: SUPABASE_TITLE,
description: SUPABASE_DESCRIPTION,
},
{
slug: "vercel-neon-drizzle",
category: "platform",
competitor: "Vercel + Neon + Drizzle",
title: VERCEL_NEON_DRIZZLE_TITLE,
description: VERCEL_NEON_DRIZZLE_DESCRIPTION,
},
] as const satisfies readonly ComparisonEntry[];

export type ComparisonSlug = (typeof COMPARISONS)[number]["slug"];
Expand Down
32 changes: 21 additions & 11 deletions apps/site/src/app/compare/page-ai-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ const SUPABASE_PROMPT = `I am choosing between Prisma Postgres and Supabase for

Ask me up to three short questions if I need Auth/Storage/Realtime out of the box, whether I want the TypeScript app hosted next to Postgres, or whether HIPAA/SOC 2/ISO 27001 matter.`;

const VERCEL_NEON_DRIZZLE_PROMPT = `I am choosing between the Prisma platform and Vercel + Neon + Drizzle for a TypeScript backend. Using only what the article states, plus my answers:

1. Tell me which fits if I want Prisma ORM, Prisma Postgres, and Prisma Compute in one platform vs if I want to wire Drizzle, Neon, and Vercel myself.
2. Call out the real tradeoffs (one platform vs three providers, Prisma schema vs Drizzle SQL-first, preview branches vs Neon branches plus Vercel previews, operations vs CU-hours plus Vercel Pro) without selling.
3. Give me the first steps to try the one you recommend.

Ask me up to three short questions if I already use Vercel, whether I prefer Drizzle's query-builder style, or whether I want the TypeScript app hosted next to Postgres.`;

const NEON_PROMPT = `I am choosing between Prisma Postgres and Neon for a TypeScript app. Using only what the article states, plus my answers:

1. Tell me which fits if I want Prisma ORM optional plus Prisma Compute in one project vs if I want Neon Functions, Auth, Object Storage, and AI Gateway with the UI elsewhere.
Expand All @@ -63,17 +71,19 @@ Ask me up to three short questions if I need a copy of production data on previe

/** Prompt every Open-with-AI logo prefills. The page's `.md` alternate is the source of truth. */
export function choosePrompt(pageMdUrl: string) {
const body = pageMdUrl.includes("/compare/planetscale.md")
? PLANETSCALE_PROMPT
: pageMdUrl.includes("/compare/supabase.md")
? SUPABASE_PROMPT
: pageMdUrl.includes("/compare/neon.md")
? NEON_PROMPT
: pageMdUrl.includes("/compare/convex.md")
? CONVEX_PROMPT
: pageMdUrl.includes("/compare/mongoose.md")
? MONGOOSE_PROMPT
: KYSELY_PROMPT;
const body = pageMdUrl.includes("/compare/vercel-neon-drizzle.md")
? VERCEL_NEON_DRIZZLE_PROMPT
: pageMdUrl.includes("/compare/planetscale.md")
? PLANETSCALE_PROMPT
: pageMdUrl.includes("/compare/supabase.md")
? SUPABASE_PROMPT
: pageMdUrl.includes("/compare/neon.md")
? NEON_PROMPT
: pageMdUrl.includes("/compare/convex.md")
? CONVEX_PROMPT
: pageMdUrl.includes("/compare/mongoose.md")
? MONGOOSE_PROMPT
: KYSELY_PROMPT;
return `Read this article and treat it as the source of truth:

${pageMdUrl}
Expand Down
11 changes: 11 additions & 0 deletions apps/site/src/app/compare/vercel-neon-drizzle.json/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NextResponse } from "next/server";
import { getVercelNeonDrizzleFacts } from "../vercel-neon-drizzle/facts";

/** Machine-readable comparison facts, each value with a source URL and dateChecked. */
export function GET() {
return NextResponse.json(getVercelNeonDrizzleFacts(), {
headers: {
"Cache-Control": "public, max-age=300, s-maxage=3600",
},
});
}
Loading
Loading