Skip to content
Back to skills

Verekia Setup

ASecurity

Verekia's preferred project setup with Next.js Pages Router, Tailwind 4, oxfmt, oxlint, and TypeScript.

  • 31 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added May 27, 2026
developmenttypescriptgoreactnextjsnodegitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add verekia/r3f-gamedev --skill verekia-setup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Verekia Setup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Verekia Setup
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/verekia-verekia-setup/badge)](https://www.skillsdirectory.com/skills/verekia-verekia-setup)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: verekia-setup
description: Verekia's preferred project setup with Next.js Pages Router, Tailwind 4, oxfmt, oxlint, and TypeScript.
---

# Verekia's Project Setup

Installation instructions and boilerplate for setting up a new R3F game project.

## package.json

```json
{
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "serve out"
  },
  "dependencies": {
    "@react-three/drei": "11.0.0-alpha.4",
    "@react-three/fiber": "10.0.0-alpha.1",
    "miniplex": "2.0.0",
    "miniplex-react": "2.0.1",
    "next": "16.1.2",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "three": "0.182.0",
    "zustand": "5.0.10"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "4.1.18",
    "@types/node": "25.0.9",
    "@types/react": "19.2.8",
    "@types/react-dom": "19.2.3",
    "@types/three": "0.182.0",
    "@verekia/warden": "0.0.3",
    "babel-plugin-react-compiler": "1.0.0",
    "oxfmt": "0.48.0",
    "oxlint": "1.63.0",
    "portless": "0.13.0",
    "postcss": "8.5.6",
    "serve": "14.2.5",
    "tailwindcss": "4.1.18",
    "typescript": "5.9.3"
  },
  "overrides": {
    "three": "0.182.0"
  },
  "patchedDependencies": {
    "three@0.182.0": "patches/three@0.182.0.patch",
    "detect-gpu@5.0.70": "patches/detect-gpu@5.0.70.patch"
  }
}
```

Use Bun as package manager and runtime.

## patches/three@0.182.0.patch

Fixes GPUShaderStage SSR error:

```patch
diff --git a/build/three.webgpu.js b/build/three.webgpu.js
index 31ff163d67f4cc5dec060a11bc7151c64f949fd9..823c1c35bca5411a27cbf52c5c3a188001fe0445 100644
--- a/build/three.webgpu.js
+++ b/build/three.webgpu.js
@@ -70604,7 +70604,7 @@ const GPUPrimitiveTopology = {
 	TriangleStrip: 'triangle-strip',
 };

-const GPUShaderStage = ( typeof self !== 'undefined' ) ? self.GPUShaderStage : { VERTEX: 1, FRAGMENT: 2, COMPUTE: 4 };
+const GPUShaderStage = ( typeof self !== 'undefined' && self.GPUShaderStage ) ? self.GPUShaderStage : { VERTEX: 1, FRAGMENT: 2, COMPUTE: 4 };

 const GPUCompareFunction = {
 	Never: 'never',
```

## patches/detect-gpu@5.0.70.patch

Adds missing exports field:

```patch
diff --git a/package.json b/package.json
index 22ffa92b457c0d83c052eab3f1961110d134d1b3..4f5f9ef7a8a80d6f85e3e5b241a87091d95bbe39 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,12 @@
   "main": "dist/detect-gpu.umd.js",
   "module": "dist/detect-gpu.esm.js",
   "types": "dist/src/index.d.ts",
+  "exports": {
+    ".": {
+      "import": "./dist/detect-gpu.esm.js",
+      "require": "./dist/detect-gpu.umd.js"
+    }
+  },
   "homepage": "https://github.com/pmndrs/detect-gpu#readme",
   "bugs": {
     "url": "https://github.com/pmndrs/detect-gpu/issues"
```

Run `bun install` to apply patches.

## next.config.mjs

Use the Pages Router with static export:

```js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  reactCompiler: true,
  output: 'export',
}

export default nextConfig
```

Pages go in `pages/` directory. Each file becomes a route. `'use client'` directives should not be used since we are in the Pages router.

## global.css

Tailwind 4 uses CSS-based configuration with `@import`. No `tailwind.config.js` needed.

```css
@import 'tailwindcss';

body {
  background-color: #111;
  color: #eee;
}

html,
body,
#__next,
canvas {
  height: 100%;
  width: 100%;
}
```

## .oxfmtrc.json

```json
{
  "$schema": "./node_modules/oxfmt/configuration_schema.json",
  "printWidth": 120,
  "tabWidth": 2,
  "useTabs": false,
  "semi": false,
  "singleQuote": true,
  "trailingComma": "all",
  "arrowParens": "avoid",
  "bracketSpacing": true,
  "sortTailwindcss": true,
  "ignorePatterns": ["node_modules", "dist", "out", ".next"]
}
```

## tsconfig.json

No `src/` folder - files live at the root. Use `@/` for absolute imports.

No need to create tsconfig.json manually. Run `bun run build` and Next.js will create it. Add the `@/` paths to the generated file:

```json
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "noEmit": true,
    "incremental": true,
    "module": "esnext",
    "esModuleInterop": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "paths": { "@/*": ["./*"] }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}
```

## .gitignore

```
node_modules/
.next/
out/
.DS_Store
.env*.local
*.tsbuildinfo
analyze/
*.blend1
next-env.d.ts
playwright-report/
.last-run.json
```

## Folder Structure

Create these folders and files:

```
entities/
  entities.tsx (empty)
models/
systems/
stores/
  core-store.ts
  ui-store.ts
  local-store.ts
lib/
  ecs.ts
  hooks.ts
  math.ts
components/
  ModelContainer.tsx
r3f.d.ts
```

## Boilerplate: r3f.d.ts

R3F type declarations for WebGPU. Make sure it's included in your `tsconfig.json`.

```ts
import type { ThreeToJSXElements } from '@react-three/fiber'
import type * as THREE from 'three/webgpu'

declare module '@react-three/fiber' {
  interface ThreeElements extends ThreeToJSXElements<typeof THREE> {}
}
```

## Boilerplate: lib/ecs.ts

```tsx
import { World } from 'miniplex'
import createReactAPI from 'miniplex-react'
import { Object3D } from 'three'

type Entity = {
  position?: { x: number; y: number; z: number }
  velocity?: { x: number; y: number; z: number }
  three?: Object3D
}

export const world = new World<Entity>()

export const { Entities } = createReactAPI(world)
```

## Boilerplate: components/ModelContainer.tsx

```tsx
import { ReactNode } from 'react'
import { Object3D } from 'three'
import { world } from '@/lib/ecs'

type Entity = {
  three?: Object3D
}

export const ModelContainer = ({ children, entity }: { children: ReactNode; entity: Entity }) => (
  <group
    ref={ref => {
      if (!ref) return
      world.addComponent(entity, 'three', ref)
      return () => {
        world.removeComponent(entity, 'three')
      }
    }}
  >
    {children}
  </group>
)
```

## Boilerplate: stores/core-store.ts

```ts
import { create } from 'zustand'

const defaultState = {
  isDebugMode: false,
}

type State = typeof defaultState
type Key = keyof State

const useCoreStore = create<State>(() => structuredClone(defaultState))
export const useCore = <K extends Key>(key: K) => useCoreStore(state => state[key])
export const getCore = useCoreStore.getState
export const setCore = <K extends Key>(...args: [key: K, value: State[K]] | [state: Partial<State>]) =>
  useCoreStore.setState(args.length === 2 ? { [args[0]]: args[1] } : args[0])
export const resetCore = () => useCoreStore.setState(structuredClone(defaultState))

// @ts-expect-error Normal
if (typeof window !== 'undefined') window.getCore = getCore
```

## Boilerplate: stores/ui-store.ts

```ts
import { create } from 'zustand'

const defaultState = {
  areSettingsOpen: false,
}

type State = typeof defaultState
type Key = keyof State

export type UIState = State
export type UIKey = Key

const useUIStore = create<State>(() => structuredClone(defaultState))
export const useUI = <K extends Key>(key: K) => useUIStore(state => state[key])
export const getUI = useUIStore.getState
export const setUI = <K extends Key>(...args: [key: K, value: State[K]] | [state: Partial<State>]) =>
  useUIStore.setState(args.length === 2 ? { [args[0]]: args[1] } : args[0])
export const resetUI = () => setUI(structuredClone(defaultState))

export const isAnyModalOpen = () => {
  const ui = getUI()
  return ui.areSettingsOpen
}

export const closeAnyModal = () => {
  if (getUI().areSettingsOpen) {
    setUI('areSettingsOpen', false)
    return true
  }
  return false
}

export const useIsAnyModalOpen = () => {
  const areSettingsOpen = useUI('areSettingsOpen')
  return areSettingsOpen
}

// @ts-expect-error Normal
if (typeof window !== 'undefined') window.getUI = getUI
```

## Boilerplate: stores/local-store.ts

```ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

const defaultState = {
  hello: 'world',
}

type State = typeof defaultState
type Key = keyof State

const useLocalStore = create<State>()(persist(() => structuredClone(defaultState), { name: 'my-game-local' }))
export const useLocal = <K extends Key>(key: K) => useLocalStore(state => state[key])
export const getLocal = useLocalStore.getState
export const setLocal = <K extends Key>(...args: [key: K, value: State[K]] | [state: Partial<State>]) =>
  useLocalStore.setState(args.length === 2 ? { [args[0]]: args[1] } : args[0])
export const resetLocal = () => setLocal(structuredClone(defaultState))

// @ts-expect-error Normal
if (typeof window !== 'undefined') window.getLocal = getLocal
```

## Boilerplate: lib/hooks.ts

```ts
import { useFrame } from '@react-three/fiber/webgpu'
import { useRef, useState } from 'react'

export const useReactive = <T>(selector: () => T, fps = 30): T => {
  const [reactiveValue, setReactiveValue] = useState<T>(selector())
  const previousValueRef = useRef(reactiveValue)

  useFrame(
    () => {
      const newValue = selector()
      if (previousValueRef.current !== newValue) {
        previousValueRef.current = newValue
        setReactiveValue(newValue)
      }
    },
    { fps },
  )

  return reactiveValue
}

export const useReactiveSlow = <T>(selector: () => T): T => useReactive(selector, 10)
export const useReactiveFast = <T>(selector: () => T): T => useReactive(selector, 30)
```

## Boilerplate: lib/math.ts

```ts
// https://lisyarus.github.io/blog/posts/exponential-smoothing.html
// Usage: `mesh.position.x += addSmoothExp(mesh.position.x, targetX, 10, dt)`
export const addSmoothExp = (current: number, target: number, speed: number, dt: number) =>
  (target - current) * (1 - Math.exp(-speed * dt))
```

---

This skill is part of [verekia](https://x.com/verekia)'s [**r3f-gamedev**](https://github.com/verekia/r3f-gamedev).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.