ワンクリックで
verekia-setup
Verekia's preferred project setup with Next.js Pages Router, Tailwind 4, oxfmt, oxlint, and TypeScript.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Verekia's preferred project setup with Next.js Pages Router, Tailwind 4, oxfmt, oxlint, and TypeScript.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Poll for changes to any value and trigger React re-renders when it changes.
Day-to-day coding style and patterns for R3F game development with Miniplex ECS.
Use Zustand as a simple state store for entity management (not a true ECS).
Attach meshes to bones of a skinned mesh, such as attaching a sword to a character's hand.
Shake the camera when the player takes damage or on impacts for visual feedback.
Display floating damage numbers that animate upward and fade out, with support for critical hits.
| name | verekia-setup |
| description | Verekia's preferred project setup with Next.js Pages Router, Tailwind 4, oxfmt, oxlint, and TypeScript. |
Installation instructions and boilerplate for setting up a new R3F game project.
{
"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.
Fixes GPUShaderStage SSR error:
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',
Adds missing exports field:
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.
Use the Pages Router with static export:
/** @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.
Tailwind 4 uses CSS-based configuration with @import. No tailwind.config.js needed.
@import 'tailwindcss';
body {
background-color: #111;
color: #eee;
}
html,
body,
#__next,
canvas {
height: 100%;
width: 100%;
}
{
"$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"]
}
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:
{
"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"]
}
node_modules/
.next/
out/
.DS_Store
.env*.local
*.tsbuildinfo
analyze/
*.blend1
next-env.d.ts
playwright-report/
.last-run.json
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
R3F type declarations for WebGPU. Make sure it's included in your tsconfig.json.
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> {}
}
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)
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>
)
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
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
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
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)
// 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's r3f-gamedev.