pb-react-spa

SKILL.md

PocketBase React SPA Skill

A skill that automates the setup of a React SPA frontend integrated with a PocketBase backend.

Tech Stack:

  • Vite — Build tool / dev server
  • React + TypeScript — UI framework
  • TanStack Router — File-based routing
  • TanStack Query — Server state management
  • Tailwind CSS + Shadcn UI — Styling / UI components
  • Biome — Linter / Formatter
  • PocketBase JS SDK — Backend communication
  • pocketbase-typegen — Type generation

Skill Resources

  • References: references/ — Integration patterns, authentication, type generation, development & deployment guides
  • PocketBase backend skill: The pocketbase skill handles backend management (collection CRUD, API rule design, etc.)

For details on the PocketBase JS SDK, also see references/js-sdk.md in the PocketBase skill.

0. Prerequisites

  • Node.js v18 or higher, npm
  • PocketBase is running (see the Bootstrap section in the pocketbase skill)
  • PocketBase collection design is complete (recommended)

1. Project Scaffolding

1-1. Project Structure

Adopt a monorepo structure with separated frontend and backend:

project-root/
├── frontend/     ← React SPA (created by this skill)
├── backend/      ← PocketBase (managed by the pocketbase skill)
│   ├── pocketbase
│   ├── pb_data/
│   ├── pb_migrations/
│   └── .env
└── README.md

Note: Check the user's existing project structure and adapt accordingly. The frontend/ directory name can be changed based on user preference.

1-2. Base Project Generation

npx create-tsrouter-app@latest frontend \
  --toolchain biome \
  --package-manager npm \
  --no-git

Note: create-tsrouter-app is a TanStack CLI wrapper where --router-only (file-based routing) is enabled by default. Tailwind CSS v4 is also set up automatically.

This command sets up the following:

  • Vite + React + TypeScript
  • TanStack Router (file-based routing)
  • Tailwind CSS v4
  • Biome (Linter / Formatter)

1-3. Adding Shadcn UI

cd frontend && npx shadcn@latest init -d

The -d flag uses default settings (new-york style, neutral color). A components.json file is created, enabling you to add components to @/components/ui/.

Example of adding components:

npx shadcn@latest add button card input label

1-4. Adding TanStack Query

npm install @tanstack/react-query

1-5. Installing PocketBase JS SDK

npm install pocketbase

1-6. Installing pocketbase-typegen

npm install -D pocketbase-typegen

1-7. Verifying the Setup

npm run build

Verify that the build completes successfully. To check the dev server, run npm run dev.

2. Post-Setup Configuration

After scaffolding, add the following configurations in order.

2-1. PocketBase Client

Create frontend/src/lib/pocketbase.ts:

import PocketBase from "pocketbase";
import type { TypedPocketBase } from "../types/pocketbase-types";

export const pb = new PocketBase() as TypedPocketBase;

pb.autoCancellation(false);

TypedPocketBase is the type generated by pocketbase-typegen. If types haven't been generated yet, temporarily use the PocketBase type directly and replace it after generation:

// Temporary version before type generation
import PocketBase from "pocketbase";
export const pb = new PocketBase();
pb.autoCancellation(false);

2-2. Vite Proxy Configuration

Edit frontend/vite.config.ts to add server.proxy.

The generated vite.config.ts has the following structure:

import { defineConfig } from "vite";
import { devtools } from "@tanstack/devtools-vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { tanstackRouter } from "@tanstack/router-plugin/vite";
import viteReact from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

const config = defineConfig({
  plugins: [
    devtools(),
    tsconfigPaths({ projects: ["./tsconfig.json"] }),
    tailwindcss(),
    tanstackRouter({ target: "react", autoCodeSplitting: true }),
    viteReact(),
  ],
  // ↓ Add this
  server: {
    proxy: {
      "/api": {
        target: "http://127.0.0.1:8090",
        changeOrigin: true,
      },
      "/_": {
        target: "http://127.0.0.1:8090",
        changeOrigin: true,
      },
    },
  },
});

export default config;

/api is the PocketBase REST API, and /_ is for PocketBase internal endpoints (realtime SSE, etc.).

Why no URL in the PocketBase client? The proxy makes all PocketBase API requests same-origin during development (browser sees localhost:5173/api/...). In production, PocketBase serves the SPA from pb_public/, which is also same-origin. Since both environments are same-origin, new PocketBase() (no arguments) works everywhere — no environment variables needed.

Important: Do not modify the generated plugins array. Only add server.proxy.

2-3. TypeScript Type Generation

Generate types while PocketBase is running:

npx pocketbase-typegen --url http://127.0.0.1:8090 --email admin@example.com --password yourpassword --out frontend/src/types/pocketbase-types.ts

Add an npm script to frontend/package.json:

{
  "scripts": {
    "typegen": "pocketbase-typegen --url http://127.0.0.1:8090 --email admin@example.com --password yourpassword --out src/types/pocketbase-types.ts"
  }
}

Confirm the superuser email and password with the user. Adjust --url to match the PocketBase URL.

Details: Read references/typegen.md

2-4. TanStack Query Configuration

Create a QueryClient and set up the Provider in the root component.

Edit frontend/src/routes/__root.tsx:

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";

export interface RouterContext {
  queryClient: QueryClient;
}

export const Route = createRootRouteWithContext<RouterContext>()({
  component: RootComponent,
});

function RootComponent() {
  return <Outlet />;
}

Create the QueryClient in frontend/src/router.tsx (or main.tsx) and pass it to the router context:

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";

const queryClient = new QueryClient();

export function getRouter() {
  const router = createTanStackRouter({
    routeTree,
    scrollRestoration: true,
    defaultPreload: "intent",
    defaultPreloadStaleTime: 0,
    context: { queryClient },
  });
  return router;
}

Important: Check the existing contents of router.tsx / main.tsx and integrate QueryClient while preserving existing configuration. The above is a pattern example — edit according to the generated file's code.

Details: Read references/react-query-pocketbase.md

2-5. Authentication Integration

If authentication is needed, configure the following:

  1. Auth context — Create an AuthProvider in frontend/src/lib/auth.tsx
  2. Protected routes — Authentication check using TanStack Router's beforeLoad
  3. Login pagefrontend/src/routes/login.tsx

Details: Read references/auth-patterns.md

3. Development Workflow

Running PocketBase and Vite Simultaneously

Terminal 1 (PocketBase):

cd backend && ./pocketbase serve --http=127.0.0.1:8090

Terminal 2 (Vite dev server):

cd frontend && npm run dev

In a Claude Code session, start PocketBase in the background:

cd backend && nohup ./pocketbase serve --http=127.0.0.1:8090 > pb.log 2>&1 &

Development Tips

  • The Vite proxy eliminates CORS issues (operates as same-origin)
  • Frontend changes are reflected instantly via HMR
  • After PocketBase collection changes, regenerate types with npm run typegen
  • The Admin UI is directly accessible at http://127.0.0.1:8090/_/

Details: Read references/dev-and-deploy.md

4. Deployment

SPA Build → PocketBase pb_public Placement

In production, PocketBase serves the SPA directly (no separate web server needed):

# Build
cd frontend && npm run build

# Place in PocketBase's pb_public
cp -r frontend/dist/* backend/pb_public/

PocketBase automatically serves files from pb_public/ and supports SPA client-side routing (non-existent paths fall back to index.html).

Docker / Docker Compose / Reverse Proxy

For production deployment including Dockerfiles (binary mode & Go package mode), Docker Compose, Caddy/Nginx reverse proxy, and executable distribution:

Details: Read references/deployment.md

5. Setup Checklist

Verification items after scaffolding is complete:

  • npm run build succeeds
  • src/lib/pocketbase.ts has been created
  • Proxy configuration has been added to vite.config.ts
  • components.json exists (Shadcn UI initialized)
  • QueryClient is configured (__root.tsx / router.tsx)
  • PocketBase JS SDK can be imported (import PocketBase from "pocketbase")
  • npm run typegen generates types (when PocketBase is running)
  • If authentication is needed: AuthProvider and protected routes are configured

6. Reference Index

Topic Reference
TanStack Query + PB integration Read references/react-query-pocketbase.md
Authentication patterns Read references/auth-patterns.md
TypeScript type generation Read references/typegen.md
Development workflow Read references/dev-and-deploy.md
Production deployment (Docker, binary, proxy) Read references/deployment.md
JS SDK details Read references/js-sdk.md in the PocketBase skill
Weekly Installs
12
First Seen
Feb 25, 2026
Installed on
gemini-cli12
github-copilot12
codex12
kimi-cli12
cursor12
amp12