| name | nextjs |
| description | This skill should be used every time you work on Next.js projects or Next.js app code changes in this repository. |
| compatibility | opencode |
Next.js Project Structure Rules
This file defines the preferred Next.js project organization for this repo, constrained to folder names that appear in the official Bulletproof React project structure.
Core Principles
- Organize by feature and domain, not by file type.
- Keep app-level orchestration in
app/ only.
- Treat
features/ as the primary unit of change and ownership.
- Share across features through top-level
components/, hooks/, lib/, types/, utils/, and config/, never by reaching into another feature.
- Keep feature boundaries explicit: local UI, logic, API calls, state, types, and utilities live together.
- Always use
next/link for internal navigation - never use <a href="..."> for internal routes.
Server Logging (MANDATORY)
- All server logs must be written to a gitignored project-local directory.
- Use
pino as the approved server logging library.
E2E Testing (MANDATORY)
- Browser/runtime E2E testing must always use the
agent-browser skill.
- The
agent-browser requirement is strict; do not substitute ad-hoc/manual browser testing.
- If a server is already running, do not kill it just to rerun tests.
Tailwind CSS v4 (MANDATORY)
Tailwind CSS v4 is the only permitted CSS framework.
Strict Rules
- v4 only - use current Tailwind v4 setup, not older Tailwind patterns.
- No CSS-in-JS - no styled-components, Emotion, or CSS modules.
- No inline styles - use Tailwind utility classes.
- Component patterns - compose complex styles with utility classes instead of custom class systems.
- Arbitrary values - use square bracket notation for one-off values such as
w-[300px].
- clsx/tailwind-merge - use
clsx and tailwind-merge for conditional classes.
Anti-Patterns (NEVER DO THESE)
- Using styled-components, Emotion, or similar
- Using
@apply to extract classes
- Using inline
style props
- Mixing Tailwind with other CSS solutions
Top-Level Layout
src/
├── app/ # application layer
├── assets/ # static assets
├── components/ # shared components
├── config/ # global configuration
├── features/ # feature based modules
├── hooks/ # shared hooks
├── lib/ # reusable libraries
├── stores/ # global state stores
├── testing/ # test utilities and mocks
├── types/ # shared types
└── utils/ # shared utility functions
Folder Semantics
app/: application glue and composition only.
routes/, app.tsx, provider.tsx, router.tsx
features/: each feature is a self-contained folder.
features/awesome-feature/
components/: shared components used across the application.
hooks/: shared hooks used across the application.
lib/: reusable libraries preconfigured for the application.
types/: shared types used across the application.
utils/: shared utility functions.
config/: global configuration and exported environment variables.
assets/: static assets such as images and fonts.
stores/: global state stores.
testing/: test utilities and mocks.
Feature Structure (Bulletproof)
Each feature owns its UI, logic, and API integration. Add only what the feature needs.
src/features/awesome-feature/
├── api/ # exported API request declarations and API hooks
├── assets/ # feature-specific static assets
├── components/ # feature-scoped UI
├── hooks/ # feature-scoped hooks
├── stores/ # feature state stores
├── types/ # feature types
└── utils/ # feature utilities
Feature Size And Atomicity
Features must stay small, focused, and explainable in one sentence. Avoid turning a single feature into a god-folder that owns multiple unrelated responsibilities.
When a feature grows too large, split it into smaller atomic features using flat names under features/:
features/<feature-name>-<subfeature>/
This keeps boundaries explicit without introducing an extra architectural layer that Bulletproof React does not define.
Good examples:
features/chat-panel-transcript/
features/chat-panel-sidebar/
features/chat-panel-summary/
features/conversation-list/
features/conversation-search/
Avoid splitting by arbitrary file type or low-level implementation detail.
Less useful examples:
features/chat-panel-buttons/
features/chat-panel-utils/
features/chat-panel-misc/
Use a separate feature when the code has its own UI, logic, state, API interaction, or user-facing responsibility. Keep code nested inside an existing feature when it is only a private implementation detail of that feature.
Rules of thumb:
- If a feature has multiple distinct user-facing responsibilities, split it.
- If a folder needs many unrelated components, hooks, stores, and types, split it.
- If a piece can be understood, tested, and replaced on its own, it can likely be its own feature.
- Keep cross-feature imports forbidden; compose features at the
app/ layer.
File Naming
- Use
kebab-case for folders and files.
- Filename matches the primary component or concern.
- One logical concern per file.
Import Rules
features/ can import from components/, hooks/, lib/, types/, utils/, config/, and stores/.
components/, hooks/, lib/, types/, utils/, config/, and stores/ never import from features/.
- Cross-feature imports are forbidden; compose features at the
app/ layer.
Example Structure
src/
├── app/
│ ├── routes/
│ ├── app.tsx
│ ├── provider.tsx
│ └── router.tsx
├── assets/
├── components/
├── config/
├── features/
│ └── awesome-feature/
│ ├── api/
│ ├── assets/
│ ├── components/
│ ├── hooks/
│ ├── stores/
│ ├── types/
│ └── utils/
├── hooks/
├── lib/
├── stores/
├── testing/
├── types/
└── utils/