chore(docs): misc

This commit is contained in:
Bereket Engida
2025-09-19 15:33:13 -07:00
committed by Alex Yang
parent ccc7c48dee
commit c456b6a2c5
12 changed files with 439 additions and 94 deletions

View File

@@ -0,0 +1,58 @@
import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const body = await request.json();
const {
name,
email,
company,
website,
userCount,
interest,
features,
additional,
} = body ?? {};
if (!name || !email) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 },
);
}
const payload = {
name,
email,
company: company ?? "",
website: website ?? "",
userCount: userCount ?? "",
interest: interest ?? "",
features: features ?? "",
additional: additional ?? "",
submittedAt: new Date().toISOString(),
userAgent: request.headers.get("user-agent") ?? undefined,
referer: request.headers.get("referer") ?? undefined,
};
const webhook = process.env.SUPPORT_WEBHOOK_URL;
if (webhook) {
try {
await fetch(webhook, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
} catch (e) {
console.error("Support webhook failed", e);
}
} else {
console.log("[support] submission", payload);
}
return NextResponse.json({ ok: true });
} catch (e) {
console.error(e);
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
}
}

View File

@@ -16,13 +16,13 @@ import { File, Folder, Files } from "fumadocs-ui/components/files";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
import { Pre } from "fumadocs-ui/components/codeblock";
import { Glow } from "../_components/default-changelog";
import { IconLink } from "../_components/changelog-layout";
import { BookIcon, GitHubIcon, XIcon } from "../_components/icons";
import { DiscordLogoIcon } from "@radix-ui/react-icons";
import { XIcon } from "../_components/icons";
import { StarField } from "../_components/stat-field";
import Image from "next/image";
import { BlogPage } from "../_components/blog-list";
import { Callout } from "@/components/ui/callout";
import { ArrowLeftIcon, ExternalLink } from "lucide-react";
import { Support } from "../_components/support";
const metaTitle = "Blogs";
const metaDescription = "Latest changes , fixes and updates.";
@@ -42,99 +42,127 @@ export default async function Page({
notFound();
}
const MDX = page.data?.body;
const toc = page.data?.toc;
const { title, description, date } = page.data;
return (
<div className="md:flex min-h-screen items-stretch relative">
<div className="bg-gradient-to-tr hidden md:block overflow-hidden px-12 py-24 md:py-0 -mt-[100px] md:h-dvh relative md:sticky top-0 from-transparent dark:via-stone-950/5 via-stone-100/30 to-stone-200/20 dark:to-transparent/10 min-h-screen flex-shrink-0 w-full md:w-1/2">
<StarField className="top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2" />
<div className="relative min-h-screen">
<div className="pointer-events-none absolute inset-0 -z-10">
<StarField className="top-1/3 left-1/2 -translate-x-1/2" />
<Glow />
<div className="flex flex-col md:justify-center max-w-xl mx-auto h-full">
<Link href="/blog" className="text-gray-600 dark:text-gray-300">
<div className="flex flex-col">
<div className="flex items-center cursor-pointer gap-x-2 text-xs w-full border-white/20">
<svg
xmlns="http://www.w3.org/2000/svg"
width="2.5em"
height="2.5em"
className="rotate-180"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M2 13v-2h16.172l-3.95-3.95l1.414-1.414L22 12l-6.364 6.364l-1.414-1.414l3.95-3.95z"
></path>
</svg>
</div>
<h1 className="mt-2 relative font-sans font-semibold tracking-tighter text-4xl mb-2 border-dashed">
{title}{" "}
</h1>
</div>
</Link>
<p className="text-gray-600 dark:text-gray-300">{description}</p>
<div className="text-gray-600 text-sm dark:text-gray-400 flex items-center gap-x-1 text-left">
By {page.data?.author.name} | {formatDate(page.data?.date)}
</div>
<div className="mt-4">
<Image
src={page.data?.image}
alt={title}
width={804}
height={452}
className="rounded-md border bg-muted transition-colors"
/>
</div>
<hr className="h-px bg-gray-300 mt-5" />
<div className="mt-8 flex flex-wrap text-gray-600 dark:text-gray-300 gap-x-1 gap-y-3 sm:gap-x-2">
<IconLink
href="/docs"
icon={BookIcon}
className="flex-none text-gray-600 dark:text-gray-300"
>
Documentation
</IconLink>
<IconLink
href="https://github.com/better-auth/better-auth"
icon={GitHubIcon}
className="flex-none text-gray-600 dark:text-gray-300"
>
GitHub
</IconLink>
<IconLink
href="https://discord.gg/better-auth"
icon={DiscordLogoIcon}
className="flex-none text-gray-600 dark:text-gray-300"
>
Community
</IconLink>
</div>
<p className="flex items-baseline absolute bottom-4 max-md:left-1/2 max-md:-translate-x-1/2 gap-x-2 text-[0.8125rem]/6 text-gray-500">
<IconLink href="https://x.com/better_auth" icon={XIcon} compact>
BETTER-AUTH.
</IconLink>
</p>
</div>
</div>
<div className="flex-1 min-h-0 h-screen overflow-y-auto px-4 relative md:px-8 pb-12 md:py-12">
<div className="absolute top-0 left-0 h-full -translate-x-full w-px bg-gradient-to-b from-black/5 dark:from-white/10 via-black/3 dark:via-white/5 to-transparent"></div>
<div className="prose">
<div className="relative mx-auto max-w-3xl px-4 md:px-0 pb-24 pt-12">
<h1 className="text-center text-3xl md:text-5xl font-semibold tracking-tighter">
{title}
</h1>
{description && (
<p className="mt-3 text-center text-muted-foreground">
{description}
</p>
)}
<div className="my-2 flex items-center justify-center gap-3">
{page.data?.author?.avatar && (
<Image
src={page.data.author.avatar}
alt={page.data?.author?.name ?? "Author"}
width={40}
height={40}
className="rounded-full border"
/>
)}
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{page.data?.author?.name && (
<span className="font-medium text-foreground">
{page.data.author.name}
</span>
)}
{page.data?.author?.twitter && (
<>
<span>·</span>
<a
href={`https://x.com/${page.data.author.twitter}`}
target="_blank"
rel="noreferrer noopener"
className="inline-flex items-center gap-1 underline decoration-dashed"
>
<XIcon className="size-3" />@{page.data.author.twitter}
</a>
</>
)}
{date && (
<>
<span>·</span>
<time dateTime={String(date)}>{formatDate(date)}</time>
</>
)}
</div>
</div>
<div className="w-full flex items-center gap-2 my-4 mb-8">
<div className="flex items-center gap-2 opacity-80">
<ArrowLeftIcon className="size-4" />
<Link href="/blog" className="">
Blogs
</Link>
</div>
<hr className="h-1 w-full opacity-80" />
</div>
<article className="prose prose-neutral dark:prose-invert mx-auto max-w-3xl px-4 md:px-0">
<MDX
components={{
...defaultMdxComponents,
Link: ({
className,
...props
}: React.ComponentProps<typeof Link>) => (
<Link
className={cn(
"font-medium underline underline-offset-4",
className,
)}
{...props}
/>
),
a: ({ className, href, children, ...props }: any) => {
const isExternal =
typeof href === "string" && /^(https?:)?\/\//.test(href);
const classes = cn(
"inline-flex items-center gap-1 font-medium underline decoration-dashed",
className,
);
if (isExternal) {
return (
<a
className={classes}
href={href}
target="_blank"
rel="noreferrer noopener"
{...props}
>
{children}
<ExternalLink className="ms-0.5 inline size-[0.9em] text-fd-muted-foreground" />
</a>
);
}
return (
<Link className={classes} href={href} {...(props as any)}>
{children}
</Link>
);
},
Link: ({ className, href, children, ...props }: any) => {
const isExternal =
typeof href === "string" && /^(https?:)?\/\//.test(href);
const classes = cn(
"inline-flex items-center gap-1 font-medium underline decoration-dashed",
className,
);
if (isExternal) {
return (
<a
className={classes}
href={href}
target="_blank"
rel="noreferrer noopener"
{...props}
>
{children}
<ExternalLink className="ms-0.5 inline size-[0.9em] text-fd-muted-foreground" />
</a>
);
}
return (
<Link className={classes} href={href} {...(props as any)}>
{children}
</Link>
);
},
Step,
Steps,
File,
@@ -164,9 +192,10 @@ export default async function Page({
{children}
</Callout>
),
Support,
}}
/>
</div>
</article>
</div>
</div>
);

View File

@@ -9,7 +9,9 @@ import { DiscordLogoIcon } from "@radix-ui/react-icons";
import Image from "next/image";
export async function BlogPage() {
const posts = blogs.getPages();
const posts = blogs.getPages().sort((a, b) => {
return new Date(b.data.date).getTime() - new Date(a.data.date).getTime();
});
return (
<div className="md:grid md:grid-cols-2 items-start">
<div className="bg-gradient-to-tr hidden md:block overflow-hidden px-12 py-24 md:py-0 -mt-[100px] md:h-dvh relative md:sticky top-0 from-transparent dark:via-stone-950/5 via-stone-100/30 to-stone-200/20 dark:to-transparent/10">
@@ -75,8 +77,8 @@ export async function BlogPage() {
<Image
src={post.data.image}
alt={post.data.title}
width={402}
height={252}
width={1206}
height={756}
className="rounded-md w-full bg-muted transition-colors"
/>
)}

View File

@@ -0,0 +1,183 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Callout } from "@/components/ui/callout";
import * as React from "react";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
export function Support() {
const [open, setOpen] = React.useState(false);
const [submitting, setSubmitting] = React.useState(false);
const formRef = React.useRef<HTMLFormElement | null>(null);
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (submitting) return;
setSubmitting(true);
const form = new FormData(event.currentTarget);
const payload = {
name: String(form.get("name") || ""),
email: String(form.get("email") || ""),
company: String(form.get("company") || ""),
website: String(form.get("website") || ""),
userCount: String(form.get("userCount") || ""),
interest: String(form.get("interest") || ""),
features: String(form.get("features") || ""),
additional: String(form.get("additional") || ""),
};
try {
const res = await fetch("/api/support", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error("Failed to submit");
setOpen(false);
formRef.current?.reset();
// optionally add a toast later
} catch (e) {
console.error(e);
// optionally add error toast
} finally {
setSubmitting(false);
}
}
return (
<Card className="flex flex-col gap-3 rounded-none">
<CardHeader>
<CardTitle>Dedicated Support</CardTitle>
<CardDescription>
We're now offering on demand support for Better Auth and Auth.js.
Including help out migrations, consultations, premium dedicated
support and more. If you're interested, please get in touch.
</CardDescription>
</CardHeader>
<CardFooter>
<Dialog open={open} onOpenChange={setOpen}>
<div>
<DialogTrigger asChild>
<Button
type="button"
className="bg-blue-500 text-white hover:bg-blue-600 transition-colors cursor-pointer"
>
Request support
</Button>
</DialogTrigger>
</div>
<DialogContent>
<DialogHeader>
<DialogTitle>Request dedicated support</DialogTitle>
<DialogDescription>
Tell us about your team and what you're looking for.
</DialogDescription>
</DialogHeader>
<form ref={formRef} className="grid gap-4" onSubmit={onSubmit}>
<div className="grid gap-2">
<Label htmlFor="name">Your name</Label>
<Input id="name" name="name" placeholder="Jane Doe" required />
</div>
<div className="grid gap-2">
<Label htmlFor="email">Work email</Label>
<Input
id="email"
name="email"
type="email"
placeholder="jane@company.com"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="company">Company</Label>
<Input id="company" name="company" placeholder="Acme Inc." />
</div>
<div className="grid gap-2">
<Label htmlFor="website">Website</Label>
<Input
id="website"
name="website"
placeholder="https://acme.com"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="userCount">Users</Label>
<Select name="userCount">
<SelectTrigger id="userCount">
<SelectValue placeholder="Select users" />
</SelectTrigger>
<SelectContent>
<SelectItem value="<1k">Less than 1k</SelectItem>
<SelectItem value="1k-10k">1k - 10k</SelectItem>
<SelectItem value=">10k">More than 10k</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="interest">What are you interested in?</Label>
<Select name="interest">
<SelectTrigger id="interest">
<SelectValue placeholder="Choose a package" />
</SelectTrigger>
<SelectContent>
<SelectItem value="migration">Migration help</SelectItem>
<SelectItem value="consultation">Consultation</SelectItem>
<SelectItem value="support">Premium support</SelectItem>
<SelectItem value="custom">Custom</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="features">
Features or plugins of interest
</Label>
<Input
id="features"
name="features"
placeholder="SAML, SIWE, WebAuthn, Organizations, ..."
/>
</div>
<div className="grid gap-2">
<Label htmlFor="additional">Anything else?</Label>
<Textarea
id="additional"
name="additional"
placeholder="Share more context, timelines, and expectations."
/>
</div>
<DialogFooter>
<Button type="submit" disabled={submitting}>
{submitting ? "Submitting..." : "Submit"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</CardFooter>
</Card>
);
}

View File

@@ -4,6 +4,7 @@ description: This migration guide aims to guide you move your auth from Supabase
date: 2025-08-25
author:
name: "Dagmawi Esayas"
avatar: "/avatars/beka.jpg"
image: "/blogs/supabase-ps.png"
tags: ["migration", "guides", "supabase", "planetscale"]
---

View File

@@ -4,7 +4,7 @@ description: "SSO with SAML, Multi Team Support, Additional Fields for Organizat
date: 2025-07-19
author:
name: "Bereket Engida"
avatar: "/blogs/bereket.png"
avatar: "/avatars/beka.jpg"
twitter: "iambereket"
image: "/release-og/1-3.png"
tags: ["1.3", "authentication", "oidc", "mcp", "sso", "organization"]

View File

@@ -0,0 +1,66 @@
---
title: Auth.js is now part of Better Auth
description: "Auth.js, formerly known as NextAuth.js, is now being maintained and overseen by Better Auth team"
date: 2025-09-22
author:
name: "Bereket Engida"
avatar: "/avatars/beka.jpg"
twitter: "imbereket"
image: "/blogs/authjs-joins.png"
tags: ["seed round", "authentication", "funding"]
---
Were excited to announce that [Auth.js](https://authjs.dev), formerly known as NextAuth.js, is now being maintained and overseen by Better Auth team. If you haven't heard of Auth.js, it has long been one of the most widely used open source authentication libraries in the JavaScript ecosystem. Chances are, if youve used [ChatGPT](https://chatgpt.com), [Google Labs](https://labs.google), [Cal.com](https://cal.com) or a million other websites, youve already interacted with Auth.js.
## Back Story about Better Auth and Auth.js
Before Better Auth, Auth.js gave developers like us the ability to own our auth without spending months wrestling with OAuth integrations or session management. But as applications became more complex and authentication needs evolved, some of its limitations became harder to ignore. We found ourselves rebuilding the same primitives over and over.
The Auth.js team recognized these challenges and had big ideas for the future, but for various reasons couldnt execute them as fully as they hoped.
That shared frustration and the vision of empowering everyone to truly own their auth started the creation of Better Auth. Since our goals aligned with the Auth.js team, we were excited to help maintain Auth.js and make auth better across the web. As we talked more, we realized that Better Auth was the best home for Auth.js.
## What does this mean for existing users?
We recognize how important this project is for countless applications, companies, and developers. If youre using Auth.js/NextAuth.js today, you can continue doing so without disruption—well keep addressing security patches and urgent issues as they come up.
But we strongly recommend new projects to start with Better Auth unless there are some very specific feature gaps (most notably stateless session management without a database). Our roadmap includes bringing those capabilities into Better Auth, so the ecosystem can converge rather than fragment.
<Callout>
For teams considering migration, weve prepared a [guide](/docs/guides/next-auth-migration-guide) and well be adding more guides and documentation soon.
</Callout>
## Final Thoughts
We are deeply grateful to the Auth.js community who have carried the project to this point. In particular, the core maintainers-[Balázs](https://x.com/balazsorban44), who served as lead maintainer, [Thang Vu](https://x.com/thanghvu),[Nico Domino](https://ndo.dev), Lluis Agusti and [Falco Winkler](https://github.com/falcowinkler)-pushed through difficult phases, brought in new primitives, and kept the project alive long enough for this transition to even be possible.
Better Auth beginning was inspired by Auth.js, and now, together, the two projects can carry the ecosystem further. The end goal remains unchanged: you should own your auth!
### Learn More
<Cards>
<Card
href="https://www.better-auth.com/docs/introduction"
title="Better Auth Setup"
>
Get started with installing Better Auth
</Card>
<Card
href="https://www.better-auth.com/docs/comparison"
title="Comparison"
>
Comparison between Better Auth and other options
</Card>
<Card
href="https://www.better-auth.com/docs/guides/next-auth-migration-guide"
title="NextAuth Migration Guide"
>
Migrate from NextAuth to Better Auth
</Card>
<Card
href="https://www.better-auth.com/docs/guides/clerk-migration-guide"
title="Clerk Migration Guide"
>
Migrate from Clerk to Better Auth
</Card>
</Cards>

View File

@@ -49,6 +49,7 @@
"clsx": "^2.1.1",
"cmdk": "1.1.1",
"date-fns": "^4.1.0",
"dotenv": "^17.2.2",
"embla-carousel-react": "^8.6.0",
"foxact": "^0.2.49",
"framer-motion": "^12.23.12",

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

View File

@@ -30,6 +30,8 @@ export const blogCollection = defineCollections({
date: z.date(),
author: z.object({
name: z.string(),
avatar: z.string(),
twitter: z.string().optional(),
}),
image: z.string(),
tags: z.array(z.string()),

3
pnpm-lock.yaml generated
View File

@@ -428,6 +428,9 @@ importers:
date-fns:
specifier: ^4.1.0
version: 4.1.0
dotenv:
specifier: ^17.2.2
version: 17.2.2
embla-carousel-react:
specifier: ^8.6.0
version: 8.6.0(react@19.1.1)