Skip to content

Commit 059b302

Browse files
sayefdeenclaude
andcommitted
feat: add Next.js demo app with landing page for Vercel deployment
- Landing page at / with features, install command, links - Live studio demo at /demo with full tRPC API - tRPC API routes at /api/trpc/* Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c5d7b44 commit 059b302

11 files changed

Lines changed: 835 additions & 24 deletions

File tree

examples/nextjs/.eslintrc.cjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** @type {import("eslint").Linter.Config} */
2+
module.exports = {
3+
root: true,
4+
extends: ["next/core-web-vitals"],
5+
rules: {
6+
"import/no-unresolved": "off",
7+
},
8+
};

examples/nextjs/next-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

examples/nextjs/next.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
transpilePackages: ["@srawad/trpc-studio"],
4+
};
5+
6+
module.exports = nextConfig;

examples/nextjs/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
7-
"build": "echo 'skip'",
7+
"build": "next build",
8+
"start": "next start",
89
"lint": "echo 'skip'"
910
},
1011
"dependencies": {
12+
"@srawad/trpc-studio": "workspace:*",
1113
"@trpc/server": "^10.45.0",
1214
"next": "^14.0.0",
1315
"react": "^18.2.0",
1416
"react-dom": "^18.2.0",
15-
"@srawad/trpc-studio": "workspace:*",
1617
"zod": "^3.22.0"
1718
},
1819
"devDependencies": {
1920
"@types/react": "^18.2.0",
20-
"@types/react-dom": "^18.2.0"
21+
"@types/react-dom": "^18.2.0",
22+
"eslint-config-next": "^14.0.0"
2123
}
2224
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
2+
3+
import { appRouter } from "~/server/router";
4+
5+
const handler = (req: Request) =>
6+
fetchRequestHandler({
7+
endpoint: "/api/trpc",
8+
req,
9+
router: appRouter,
10+
createContext: () => ({}),
11+
});
12+
13+
export { handler as GET, handler as POST };

examples/nextjs/src/app/api/panel/route.ts renamed to examples/nextjs/src/app/demo/route.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import { NextResponse } from "next/server";
2+
import { renderTrpcStudio } from "@srawad/trpc-studio";
23

34
import { appRouter } from "~/server/router";
45

5-
export async function GET() {
6-
if (process.env.NODE_ENV !== "development") {
7-
return new NextResponse("Not Found", { status: 404 });
8-
}
9-
10-
const { renderTrpcStudio } = await import("@srawad/trpc-studio");
11-
6+
export function GET() {
127
return new NextResponse(
138
renderTrpcStudio(appRouter, {
149
url: "/api/trpc",
15-
meta: { title: "My API" },
10+
meta: {
11+
title: "trpc-studio Demo",
12+
description: "A live demo of trpc-studio with an example tRPC API",
13+
version: "0.1.5",
14+
},
1615
}),
1716
{ status: 200, headers: { "Content-Type": "text/html" } },
1817
);

examples/nextjs/src/app/layout.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Metadata } from "next";
2+
3+
export const metadata: Metadata = {
4+
title: "trpc-studio — Swagger-like UI for tRPC",
5+
description:
6+
"A developer UI for tRPC APIs with auto-generated input forms, Try It Out execution, and output type visualization.",
7+
};
8+
9+
export default function RootLayout({ children }: { children: React.ReactNode }) {
10+
return (
11+
<html lang="en">
12+
<body>{children}</body>
13+
</html>
14+
);
15+
}

examples/nextjs/src/app/page.tsx

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
export default function Home() {
2+
return (
3+
<div
4+
style={{
5+
minHeight: "100vh",
6+
display: "flex",
7+
flexDirection: "column",
8+
alignItems: "center",
9+
justifyContent: "center",
10+
fontFamily: "system-ui, -apple-system, sans-serif",
11+
background: "linear-gradient(135deg, #0f172a 0%, #1e293b 100%)",
12+
color: "#f8fafc",
13+
padding: "2rem",
14+
}}
15+
>
16+
<div style={{ maxWidth: "720px", textAlign: "center" }}>
17+
<h1
18+
style={{
19+
fontSize: "3rem",
20+
fontWeight: 800,
21+
marginBottom: "1rem",
22+
background: "linear-gradient(to right, #60a5fa, #a78bfa)",
23+
WebkitBackgroundClip: "text",
24+
WebkitTextFillColor: "transparent",
25+
}}
26+
>
27+
trpc-studio
28+
</h1>
29+
30+
<p style={{ fontSize: "1.25rem", color: "#94a3b8", marginBottom: "2rem", lineHeight: 1.6 }}>
31+
A Swagger-like developer UI for tRPC APIs with auto-generated input forms, real-time
32+
execution, and output type visualization via the TypeScript Compiler API.
33+
</p>
34+
35+
<div style={{ display: "flex", gap: "1rem", justifyContent: "center", flexWrap: "wrap" }}>
36+
<a
37+
href="/demo"
38+
style={{
39+
padding: "0.75rem 2rem",
40+
background: "#3b82f6",
41+
color: "#fff",
42+
borderRadius: "0.5rem",
43+
textDecoration: "none",
44+
fontWeight: 600,
45+
fontSize: "1rem",
46+
}}
47+
>
48+
Live Demo
49+
</a>
50+
<a
51+
href="https://github.com/sayefdeen/trpc-studio"
52+
style={{
53+
padding: "0.75rem 2rem",
54+
background: "transparent",
55+
color: "#e2e8f0",
56+
borderRadius: "0.5rem",
57+
textDecoration: "none",
58+
fontWeight: 600,
59+
fontSize: "1rem",
60+
border: "1px solid #475569",
61+
}}
62+
>
63+
GitHub
64+
</a>
65+
<a
66+
href="https://www.npmjs.com/package/@srawad/trpc-studio"
67+
style={{
68+
padding: "0.75rem 2rem",
69+
background: "transparent",
70+
color: "#e2e8f0",
71+
borderRadius: "0.5rem",
72+
textDecoration: "none",
73+
fontWeight: 600,
74+
fontSize: "1rem",
75+
border: "1px solid #475569",
76+
}}
77+
>
78+
npm
79+
</a>
80+
</div>
81+
82+
<div
83+
style={{
84+
marginTop: "3rem",
85+
background: "#1e293b",
86+
border: "1px solid #334155",
87+
borderRadius: "0.5rem",
88+
padding: "1rem 1.5rem",
89+
textAlign: "left",
90+
}}
91+
>
92+
<code style={{ color: "#94a3b8", fontSize: "0.875rem" }}>
93+
<span style={{ color: "#64748b" }}>$</span>{" "}
94+
<span style={{ color: "#f8fafc" }}>npm install -D @srawad/trpc-studio</span>
95+
</code>
96+
</div>
97+
98+
<div
99+
style={{
100+
marginTop: "3rem",
101+
display: "grid",
102+
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
103+
gap: "1.5rem",
104+
textAlign: "left",
105+
}}
106+
>
107+
{[
108+
{
109+
title: "Auto Input Forms",
110+
desc: "Generated from Zod schemas — string, number, boolean, enum, object, array",
111+
},
112+
{
113+
title: "Try It Out",
114+
desc: "Execute real tRPC calls from the browser with response display and timing",
115+
},
116+
{
117+
title: "Output Types",
118+
desc: "Extract response types at build time using the TypeScript Compiler API",
119+
},
120+
{
121+
title: "Self-Contained",
122+
desc: "Served as a single HTML response — no static file hosting needed",
123+
},
124+
].map((feature) => (
125+
<div
126+
key={feature.title}
127+
style={{
128+
background: "#1e293b",
129+
border: "1px solid #334155",
130+
borderRadius: "0.5rem",
131+
padding: "1.25rem",
132+
}}
133+
>
134+
<h3 style={{ fontSize: "1rem", fontWeight: 600, marginBottom: "0.5rem" }}>
135+
{feature.title}
136+
</h3>
137+
<p style={{ fontSize: "0.875rem", color: "#94a3b8", lineHeight: 1.5, margin: 0 }}>
138+
{feature.desc}
139+
</p>
140+
</div>
141+
))}
142+
</div>
143+
</div>
144+
</div>
145+
);
146+
}

examples/nextjs/src/server/router.ts

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,92 @@ import { z } from "zod";
33

44
const t = initTRPC.create();
55

6-
export const appRouter = t.router({
7-
hello: t.procedure.input(z.object({ name: z.string().optional() })).query(({ input }) => {
8-
return { greeting: `Hello ${input.name ?? "world"}!` };
6+
const userRouter = t.router({
7+
getById: t.procedure.input(z.object({ id: z.string() })).query(({ input }) => {
8+
return {
9+
id: input.id,
10+
name: "Jane Smith",
11+
email: "jane@example.com",
12+
role: "admin" as const,
13+
createdAt: new Date().toISOString(),
14+
};
915
}),
1016

11-
user: t.router({
12-
getById: t.procedure.input(z.object({ id: z.string() })).query(({ input }) => {
13-
return { id: input.id, name: "John Doe", email: "john@example.com" };
17+
list: t.procedure
18+
.input(
19+
z
20+
.object({
21+
limit: z.number().min(1).max(100).default(10),
22+
offset: z.number().min(0).default(0),
23+
})
24+
.optional(),
25+
)
26+
.query(({ input }) => {
27+
return {
28+
users: [
29+
{ id: "1", name: "Jane Smith", email: "jane@example.com" },
30+
{ id: "2", name: "John Doe", email: "john@example.com" },
31+
],
32+
total: 2,
33+
limit: input?.limit ?? 10,
34+
offset: input?.offset ?? 0,
35+
};
36+
}),
37+
38+
create: t.procedure
39+
.input(
40+
z.object({
41+
name: z.string().min(1),
42+
email: z.string().email(),
43+
role: z.enum(["admin", "user", "moderator"]).default("user"),
44+
tags: z.array(z.string()).optional(),
45+
}),
46+
)
47+
.mutation(({ input }) => {
48+
return {
49+
id: Math.random().toString(36).slice(2),
50+
...input,
51+
createdAt: new Date().toISOString(),
52+
};
1453
}),
54+
});
55+
56+
const postRouter = t.router({
57+
getById: t.procedure.input(z.object({ id: z.string() })).query(({ input }) => {
58+
return {
59+
id: input.id,
60+
title: "Getting Started with tRPC",
61+
content: "tRPC allows you to build fully typesafe APIs...",
62+
author: { id: "1", name: "Jane Smith" },
63+
published: true,
64+
tags: ["trpc", "typescript", "tutorial"],
65+
};
66+
}),
67+
68+
create: t.procedure
69+
.input(
70+
z.object({
71+
title: z.string().min(1),
72+
content: z.string(),
73+
published: z.boolean().default(false),
74+
tags: z.array(z.string()).optional(),
75+
}),
76+
)
77+
.mutation(({ input }) => {
78+
return {
79+
id: Math.random().toString(36).slice(2),
80+
...input,
81+
createdAt: new Date().toISOString(),
82+
};
83+
}),
84+
});
85+
86+
export const appRouter = t.router({
87+
health: t.procedure.query(() => {
88+
return { status: "ok", timestamp: new Date().toISOString() };
1589
}),
90+
user: userRouter,
91+
post: postRouter,
1692
});
1793

1894
export type AppRouter = typeof appRouter;

examples/nextjs/tsconfig.json

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
{
2-
"extends": "../../tsconfig.base.json",
32
"compilerOptions": {
4-
"jsx": "preserve",
3+
"target": "ES2017",
4+
"lib": ["dom", "dom.iterable", "esnext"],
5+
"allowJs": true,
6+
"skipLibCheck": true,
7+
"strict": true,
58
"noEmit": true,
9+
"esModuleInterop": true,
10+
"module": "esnext",
11+
"moduleResolution": "bundler",
12+
"resolveJsonModule": true,
13+
"isolatedModules": true,
14+
"jsx": "preserve",
15+
"incremental": true,
16+
"plugins": [{ "name": "next" }],
617
"paths": {
718
"~/*": ["./src/*"]
819
}
920
},
10-
"include": ["src"]
21+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22+
"exclude": ["node_modules"]
1123
}

0 commit comments

Comments
 (0)