Skip to content

Commit d3de1ca

Browse files
Refactor: Improve /sandboxes performance + implement tRPC (#186)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Introduce tRPC + React Query with SSR hydration, migrate sandboxes data/metrics to tRPC, add auth/telemetry middlewares, refactor polling, and update caching, intervals, and deps. > > - **Backend (tRPC)**: > - Add tRPC stack with `/api/trpc` route, superjson, and server/client helpers (`init`, `server`, `client`, `query-client`). > - Implement routers: `sandboxes` (list, metrics, team metrics/max) and `teams.getLimits` with input schemas and error helpers. > - Add middlewares: auth (Supabase session/user) and telemetry (tracing, metrics, structured logs). > - Remove legacy server actions `get-team-sandboxes*` and switch to tRPC. > - **Frontend (Data & UI)**: > - Migrate sandboxes list to tRPC + React Query with SSR prefetch/hydration; use `HydrateClient`, `prefetch`, `useSuspenseQuery`. > - Replace SWR with React Query in metrics hook; update `SandboxesHeader` props and refresh wiring. > - Refactor `PollingButton` API and implement `usePolling`; update refresh controls usage. > - Adjust sandboxes page layout, loading, and suspense boundaries. > - **Auth & Caching**: > - Introduce `check-user-team-auth-cached` with Next cache tags; update callers and tests; tweak `CACHE_TAGS.USER_TEAM_AUTHORIZATION` signature. > - Revalidate auth cache tags on team member add/remove. > - **Config**: > - Tune intervals (`SANDBOXES_METRICS_POLLING_MS` to 5s) and table polling options (add `5s`, change default). > - **Dependencies & Scripts**: > - Add `@tanstack/react-query`, `@trpc/*`, `superjson`; add `clean` script; wire TRPC provider into client providers. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d485d21. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude <[email protected]>
1 parent ff82528 commit d3de1ca

File tree

37 files changed

+1473
-434
lines changed

37 files changed

+1473
-434
lines changed

bun.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"<<<<<<< Development": "",
3030
"shad": "bunx shadcn@canary",
3131
"test:development:traffic": "vitest run src/__test__/development/traffic.test.ts",
32+
"clean": "rm -rf .next node_modules && bun install",
3233
"<<<<<<< Testing": "",
3334
"test:run": "bun scripts:check-all-env && vitest run",
3435
"test:integration": "bun scripts:check-app-env && vitest run src/__test__/integration/",
@@ -78,9 +79,14 @@
7879
"@supabase/ssr": "^0.5.2",
7980
"@supabase/supabase-js": "^2.48.1",
8081
"@tanstack/match-sorter-utils": "^8.19.4",
82+
"@tanstack/react-query": "^5.90.7",
8183
"@tanstack/react-table": "^8.20.6",
8284
"@tanstack/react-virtual": "^3.11.3",
8385
"@theguild/remark-mermaid": "^0.2.0",
86+
"@trpc/client": "^11.7.1",
87+
"@trpc/react-query": "^11.7.1",
88+
"@trpc/server": "^11.7.1",
89+
"@trpc/tanstack-react-query": "^11.7.1",
8490
"@types/mdx": "^2.0.13",
8591
"@types/micromatch": "^4.0.9",
8692
"@vercel/analytics": "^1.5.0",
@@ -132,6 +138,7 @@
132138
"shiki": "3.2.1",
133139
"sonner": "^2.0.7",
134140
"styled-components": "^6.1.19",
141+
"superjson": "^2.2.5",
135142
"swr": "^2.3.4",
136143
"tailwind-merge": "^3.3.1",
137144
"tw-animate-css": "^1.3.6",

src/__test__/integration/resolve-user-team.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ vi.mock('next/headers', () => ({
4040
cookies: vi.fn(() => mockCookieStore),
4141
}))
4242

43-
vi.mock('@/server/auth/check-user-team-auth', () => ({
44-
default: mockCheckUserTeamAuth,
43+
vi.mock('@/server/auth/check-user-team-auth-cached', () => ({
44+
checkUserTeamAuth: mockCheckUserTeamAuth,
4545
}))
4646

4747
// import after mocks are set up

src/app/api/trpc/[trpc]/route.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
2+
import { type NextRequest } from 'next/server'
3+
4+
import { createTRPCContext } from '@/server/api/init'
5+
import { trpcAppRouter } from '@/server/api/routers'
6+
7+
/**
8+
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
9+
* handling a HTTP request (e.g. when you make requests from Client Components).
10+
*/
11+
const createContext = async (req: NextRequest) => {
12+
return createTRPCContext({
13+
headers: req.headers,
14+
})
15+
}
16+
17+
const handler = (req: NextRequest) =>
18+
fetchRequestHandler({
19+
endpoint: '/api/trpc',
20+
req,
21+
router: trpcAppRouter,
22+
createContext: () => createContext(req),
23+
})
24+
25+
export { handler as GET, handler as POST }

src/app/dashboard/[teamIdOrSlug]/sandboxes/(tabs)/@list/loading.tsx

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,24 @@
1+
import LoadingLayout from '@/features/dashboard/loading-layout'
12
import SandboxesTable from '@/features/dashboard/sandboxes/list/table'
2-
import { getTeamSandboxes } from '@/server/sandboxes/get-team-sandboxes'
3-
import { getTeamSandboxesMetrics } from '@/server/sandboxes/get-team-sandboxes-metrics'
4-
import ErrorBoundary from '@/ui/error'
3+
import { HydrateClient, prefetch, trpc } from '@/trpc/server'
4+
import { Suspense } from 'react'
55

66
export default async function ListPage({
77
params,
88
}: PageProps<'/dashboard/[teamIdOrSlug]/sandboxes'>) {
99
const { teamIdOrSlug } = await params
1010

11-
const sandboxesRes = await getTeamSandboxes({ teamIdOrSlug })
12-
13-
if (!sandboxesRes?.data || sandboxesRes?.serverError) {
14-
return (
15-
<ErrorBoundary
16-
error={
17-
{
18-
name: 'Sandboxes Error',
19-
message: sandboxesRes?.serverError ?? 'Unknown error',
20-
} satisfies Error
21-
}
22-
description="Could not load sandboxes"
23-
/>
24-
)
25-
}
26-
27-
const maxSandboxesToFetchInitially = 100
28-
29-
const metricsRes = await getTeamSandboxesMetrics({
30-
teamIdOrSlug,
31-
sandboxIds: sandboxesRes.data.sandboxes
32-
.map((sandbox) => sandbox.sandboxID)
33-
.slice(0, maxSandboxesToFetchInitially),
34-
})
35-
36-
const sandboxes = sandboxesRes.data.sandboxes
11+
await prefetch(
12+
trpc.sandboxes.getSandboxes.queryOptions({
13+
teamIdOrSlug,
14+
})
15+
)
3716

3817
return (
39-
<SandboxesTable
40-
initialSandboxes={sandboxes}
41-
initialMetrics={metricsRes?.data?.metrics || null}
42-
/>
18+
<HydrateClient>
19+
<Suspense fallback={<LoadingLayout />}>
20+
<SandboxesTable />
21+
</Suspense>
22+
</HydrateClient>
4323
)
4424
}

src/app/dashboard/[teamIdOrSlug]/sandboxes/(tabs)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DashboardTab, DashboardTabs } from '@/ui/dashboard-tabs'
22
import { ListIcon, TrendIcon } from '@/ui/primitives/icons'
33

4-
export default async function SandboxesLayout({
4+
export default function SandboxesLayout({
55
list,
66
monitoring,
77
}: LayoutProps<'/dashboard/[teamIdOrSlug]/sandboxes'> & {

src/configs/cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
* Centralized cache tag configuration for Next.js "use cache" directive.
33
*/
44
export const CACHE_TAGS = {
5-
USER_TEAM_AUTHORIZATION: (userId: string, teamIdOrSlug: string) =>
6-
`user-team-authorization-${userId}-${teamIdOrSlug}`,
5+
USER_TEAM_AUTHORIZATION: (userId: string, teamId: string) =>
6+
`user-team-authorization-${userId}-${teamId}`,
77
USER_TEAMS: (userId: string) => `user-teams-${userId}`,
88

99
TEAM_ID_FROM_SEGMENT: (segment: string) => `team-id-from-segment-${segment}`,

src/configs/intervals.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const SANDBOXES_METRICS_POLLING_MS = 3_000
1+
export const SANDBOXES_METRICS_POLLING_MS = 5_000
22

33
export const SANDBOXES_DETAILS_METRICS_POLLING_MS = 3_000
44

src/features/client-providers.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client'
22

3+
import { TRPCReactProvider } from '@/trpc/client'
34
import { Toaster } from '@/ui/primitives/sonner'
45
import { ToastProvider } from '@/ui/primitives/toast'
56
import { TooltipProvider } from '@/ui/primitives/tooltip'
@@ -13,20 +14,22 @@ interface ClientProvidersProps {
1314

1415
export default function ClientProviders({ children }: ClientProvidersProps) {
1516
return (
16-
<NuqsAdapter>
17-
<PostHogProvider>
18-
<ThemeProvider
19-
attribute="class"
20-
defaultTheme="system"
21-
enableSystem
22-
disableTransitionOnChange
23-
>
24-
<TooltipProvider>
25-
<ToastProvider>{children}</ToastProvider>
26-
<Toaster />
27-
</TooltipProvider>
28-
</ThemeProvider>
29-
</PostHogProvider>
30-
</NuqsAdapter>
17+
<TRPCReactProvider>
18+
<NuqsAdapter>
19+
<PostHogProvider>
20+
<ThemeProvider
21+
attribute="class"
22+
defaultTheme="system"
23+
enableSystem
24+
disableTransitionOnChange
25+
>
26+
<TooltipProvider>
27+
<ToastProvider>{children}</ToastProvider>
28+
<Toaster />
29+
</TooltipProvider>
30+
</ThemeProvider>
31+
</PostHogProvider>
32+
</NuqsAdapter>
33+
</TRPCReactProvider>
3134
)
3235
}

0 commit comments

Comments
 (0)