You are writing a Devvit web application that will be executed on Reddit.com. To learn more about Devvit, use the devvit-mcp if configured or go to https://developers.reddit.com/docs/llms.txt.
- Frontend: React 19, Tailwind CSS 4, Vite
- Backend: Node.js serverless environment (Devvit), Hono, TRPC
- Communication: tRPC v11 for end-to-end type safety
- Testing: Vitest
/src/server: Backend Code. This runs in a secure, serverless environment.trpc.ts: Defines the API router and procedures.index.ts: Main server entry point (Hono app).- Access
redis,reddit, andcontexthere via@devvit/web/server.
/src: Frontend Code. This runs in the user's browser (WebView).game.tsx: The main React entry point (Expanded View).splash.tsx: The initial React entry point (Inline View).trpc.ts: The tRPC client instance.- Access navigation and UI utilities here via
@devvit/web/client.
This project uses tRPC for communication between the client and server.
- Define Procedure: Add a new query or mutation in
src/server/trpc.ts. - Call in Client: Use
trpc.procedureName.query()or.mutate()in your React components.
Devvit platform features like Menu Items, Forms, and Triggers are handled via Hono routes and devvit.json configuration.
- Define Route: Add a route in
src/server/routes/(e.g.,menu.ts) and mount it insrc/server/index.tsunder/internal. - Configure: Add the mapping in
devvit.jsonpointing to the route (e.g.,/internal/menu/post-create).
This template uses a two-stage WebView pattern:
- Inline View (
splash.tsx): The default view shown in the feed. Defined asdefaultindevvit.json. UserequestExpandedModeto transition to the game view. - Expanded View (
game.tsx): The immersive view. Defined asgameindevvit.json.
- After making changes, run
npm run type-checkto make sure the Typescript types are compiling correctly. - Use
npm run test -- my-file-nameto run isolated tests against files
- Prefer type aliases over interfaces when writing typescript
- Prefer named exports over default exports
- Never cast typescript types
For all server tests, utilize the initialized @devvit/test harness located here: src/server/test.ts. It is a Vitest compatible API that runs in-memory mocks for all of the @devvit/web/server capabilities. This makes it to where you will rarely need to use or reset mocks (except for the reddit API where you will receive a helpful error when you need to mock). Each test runs completely isolated from another so you should not need beforeAll, afterAll, or similar lifecycle hooks either.
For example, given this file:
// src/server/core/increment.ts
import { redis } from '@devvit/web/server';
const key = 'count';
export const countGet = async () => {
return Number((await redis.get(key)) ?? 0);
};
export const countIncrement = async () => {
return await redis.incrBy(key, 1);
};
export const countDecrement = async () => {
return await redis.incrBy(key, -1);
};The tests can be:
// src/server/core/increment.test.ts
import { expect } from 'vitest';
import { test } from '../test';
import { countDecrement, countGet, countIncrement } from './increment';
test('Should increment the count', async () => {
const count = await countGet();
expect(count).toBe(0);
const newCount = await countIncrement();
expect(newCount).toBe(1);
});
test('Should decrement the count', async () => {
// Note how this is running against the same key as the previous function
// and no mocks or resetting of mocks was needed!
const count = await countGet();
expect(count).toBe(0);
const newCount = await countDecrement();
expect(newCount).toBe(-1);
});Learn more about the test harness here: https://developers.reddit.com/docs/next/guides/tools/devvit_test
- You may find code that references blocks or
@devvit/public-apiwhile building a feature. Do NOT use this code as this project is configured to use Devvit web only.