Skip to content

Instantly share code, notes, and snippets.

@awb305
Last active May 21, 2025 02:46
Show Gist options
  • Select an option

  • Save awb305/f277201f94db1e9e514c6fd01227c9f9 to your computer and use it in GitHub Desktop.

Select an option

Save awb305/f277201f94db1e9e514c6fd01227c9f9 to your computer and use it in GitHub Desktop.
.cursorrules

.cursorrules

Project Instructions

Use the project specification and guidelines as you build the app.

Write the complete code for every step. Do not get lazy.

Your goal is to completely finish whatever I ask for.

Tech Stack

Project Structure

Below is a recommended structure for your Hono + React (SPA) application with TanStack Router. Feel free to adapt it as needed but keep the separation of logic from presentation.

├─ server/ │ ├─ index.ts # Hono server entry point │ ├─ routes/ # Hono routes (REST endpoints, etc.) │ ├─ db/ # Drizzle setup/config (Postgres) │ └─ ... ├─ client/ │ ├─ main.tsx # ReactDOM.createRoot entry for the SPA │ ├─ App.tsx # Main React component for the app │ ├─ router/ │ │ ├─ index.ts # Exports the main TanStack Router instance or config │ │ ├─ root.route.ts # Example “root” route (TanStack Router) │ │ ├─ dash/ │ │ │ ├─ index.route.ts # Example route for /dash │ │ │ └─ settings.route.ts # Example route for /dash/settings │ │ └─ route-trees.ts # Example file exporting structured route trees │ ├─ logic/ # Business logic, custom hooks, data fetching │ ├─ components/ │ │ └─ ui/ # shadcn components (e.g., button.tsx, badge.tsx, etc.) │ ├─ lib/ # Client-side utility code │ ├─ assets/ # Static/media assets │ └─ ... ├─ types/ # Shared type definitions (client + server) │ ├─ index.ts # Barrel export for all types │ └─ ... ├─ vite.config.ts # Vite configuration ├─ .env.example # Example environment file ├─ .env.local # Local environment file (never commit) └─ ...

Rules

Follow these rules when building the project.

General Rules

  • Use @ to import anything from the project unless otherwise specified
  • End-to-End Type Safety

Naming Conventions

  • Use kebab-case for filenames and folder names unless otherwise specified.

Environment Rules

  1. Environment Variables

    • If you update environment variables, update the .env.example file accordingly.
    • All environment variables should go in .env.local (never committed).
    • Do not expose sensitive variables to the frontend.
    • For Vite-based client-side env variables:
      • Vite only exposes env variables prefixed with VITE_.
      • Access them using import.meta.env.VITE_SOME_VARIABLE in your client code.
      • Example .env.local:
        VITE_API_URL="https://api.example.com"
        
      • Then in client code:
        const apiUrl = import.meta.env.VITE_API_URL;
  2. Server-Side Access

    • Access environment variables in Hono server code (using process.env.VARIABLE_NAME), but do not leak secrets.

Type Rules

  1. Type Importing

    • When importing types, use @/types for clarity.
    • Example: import { MyType } from "@/types".
  2. Filename Conventions

    • Name type definition files like example-types.ts.
    • All types should go in the types folder; re-export them from types/index.ts.
  3. Prefer Interfaces

    • Prefer interfaces over type aliases for data shapes, unless a union type or advanced features require a type alias.

Example:

// types/actions-types.ts

export type ActionState<T> =
  | { isSuccess: true; message: string; data: T }
  | { isSuccess: false; message: string; data?: never };

then export from types/index.ts

// types/index.ts
export * from './actions-types';

Frontend Rules

We are building a Single Page Application (SPA) with TanStack Router and React. No React Server Components are required. Prefer shadcn components for UI elements.

  1. Structure

    • Put route definitions in client/router/.
    • Keep logic (data fetching, API calls, state mgmt, custom hooks) in client/logic/.
    • Store shadcn components in client/components/ui/.
  2. Routing

    • All routing is handled by TanStack Router. Keep your route definitions strongly typed.
    • Use asynchronous data fetching as needed; prefer to keep business logic in client/logic/ or custom hooks.
  3. Components

    • Use div unless another HTML tag is a better semantic fit.
    • Separate main parts of a component’s JSX with extra blank lines for clarity.
    • Use lucide-react for icons.
    • Keep presentational components “dumb” if possible (handle data manipulation in your logic layer).
    • When using shadcn components, keep them in client/components/ui/ for consistency and ease of updates.
  4. Fetching Data

    • Use useQuery from @tanstack/react-query for fetching data.
    • Use useMutation from @tanstack/react-query for creating, updating, and deleting data.
    • In general, prefer to use useQuery and render a shadcn skeleton component while the data is loading. Stay away from useEffect and useState for data fetching.

Example:

// client/components/ui/example-component.tsx
import { Button } from '@/client/components/ui/button';
import { LucideIcon } from 'lucide-react';

interface ExampleProps {
  items: string[];
}

export function ExampleComponent({ items }: ExampleProps) {
  return (
    <div className="p-4">
      {items.map((item) => (
        <div key={item}>{item}</div>
      ))}

      <Button variant="default">
        <LucideIcon name="check" />
        Click Me
      </Button>
    </div>
  );
}

Server Rules

Since we use Hono for the server:

  1. Hono Setup

    • Create routes in server/routes.
    • Mount them in server/index.ts or a similar entry file:
    import { Hono } from 'hono';
    import { exampleRoute } from '@/server/routes/example-route';
    
    // Example typed Env binding (see Hono Stacks docs)
    interface Bindings {
      DATABASE_URL: string;
    }
    
    const app = new Hono<Bindings>();
    
    app.route('/example', exampleRoute);
    
    export default app;

Client Server Communication

Follow the Hono Stacks for client server communication.

  1. Hono is used for the API.

Example:

const route = app.get(
  '/hello',
  zValidator('query', z.object({ name: z.string() })),
  (c) => {
    const { name } = c.req.valid('query');
    return c.json({ message: `Hello! ${name}` });
  }
);
  1. Validate with Zod to receive the value of the query parameter.

Example:

import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

app.get(
  '/hello',
  zValidator(
    'query',
    z.object({
      name: z.string()
    })
  ),
  (c) => {
    const { name } = c.req.valid('query');
    return c.json({
      message: `Hello! ${name}`
    });
  }
);
  1. Sharing the Types
  • To emit an endpoint specification, export its type.
const route = app.get(
  '/hello',
  zValidator(
    'query',
    z.object({
      name: z.string()
    })
  ),
  (c) => {
    const { name } = c.req.valid('query');
    return c.json({
      message: `Hello! ${name}`
    });
  }
);
export type AppType = typeof route;
  1. Create a client object by passing the AppType type to hc as generics.
import { AppType } from './server';
import { hc } from 'hono/client';

const client = hc<AppType>('/api');
const res = await client.hello.$get({
  query: {
    name: 'Hono'
  }
});

Client Server Example with Hono and React

API Server:

// functions/api/[[route]].ts
import { Hono } from 'hono';
import { handle } from 'hono/cloudflare-pages';
import { z } from 'zod';
import { zValidator } from '@hono/zod-validator';

const app = new Hono();

const schema = z.object({
  id: z.string(),
  title: z.string()
});

type Todo = z.infer<typeof schema>;

const todos: Todo[] = [];

const route = app
  .post('/todo', zValidator('form', schema), (c) => {
    const todo = c.req.valid('form');
    todos.push(todo);
    return c.json({
      message: 'created!'
    });
  })
  .get((c) => {
    return c.json({
      todos
    });
  });

export type AppType = typeof route;

export const onRequest = handle(app, '/api');

The client with React and React Query:

// src/App.tsx
import {
  useQuery,
  useMutation,
  QueryClient,
  QueryClientProvider
} from '@tanstack/react-query';
import { AppType } from '../functions/api/[[route]]';
import { hc, InferResponseType, InferRequestType } from 'hono/client';

const queryClient = new QueryClient();
const client = hc<AppType>('/api');

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  );
}

const Todos = () => {
  const query = useQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const res = await client.todo.$get();
      return await res.json();
    }
  });

  const $post = client.todo.$post;

  const mutation = useMutation<
    InferResponseType<typeof $post>,
    Error,
    InferRequestType<typeof $post>['form']
  >(
    async (todo) => {
      const res = await $post({
        form: todo
      });
      return await res.json();
    },
    {
      onSuccess: async () => {
        queryClient.invalidateQueries({ queryKey: ['todos'] });
      },
      onError: (error) => {
        console.log(error);
      }
    }
  );

  return (
    <div>
      <button
        onClick={() => {
          mutation.mutate({
            id: Date.now().toString(),
            title: 'Write code'
          });
        }}
      >
        Add Todo
      </button>

      <ul>
        {query.data?.todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

Database Rules

  • All database operation should go through drizzle ORM on the server.
  • Use the db/schema.ts file to define the database schema.
  • Use the createdAt and updatedAt columns to track when a row was created and last updated.
  • When creating a relationship, always create a many to many relationship, even if it is a one to one relationship.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment