- Introduction
- Previous State
- Current Architecture
- Directory Structure
- Patterns and Conventions
- Developer Guide
- Migration Details
This document describes KanaDojo's architecture, a Japanese learning platform built with Next.js 15, React 19, and TypeScript. The current architecture is based on a feature-based pattern, which organizes code by functionality rather than by file type.
KanaDojo has undergone a complete architectural transformation. Previously, the project had no defined architecture, which resulted in:
- 🔴 Blind development: Developers didn't know where to place new code
- 🔴 Scattered code: Components, logic, and data mixed without clear structure
- 🔴 Difficult maintenance: Changes to one functionality affected multiple locations
- 🔴 Poor scalability: Adding new features was complicated and error-prone
- 🔴 Slow onboarding: New developers took time to understand the organization
With the new feature-based architecture, these problems have been systematically resolved.
kanadojo/ (OLD STRUCTURE - DO NOT USE)
├── components/
│ ├── Dojo/
│ │ ├── Kana/ # Mixed Kana components
│ │ ├── Kanji/ # Mixed Kanji components
│ │ └── Vocab/ # Mixed Vocabulary components
│ ├── reusable/ # Uncategorized "reusable" components
│ ├── Settings/ # Settings isolated from rest
│ └── ui/ # shadcn/ui components
│
├── store/ # All stores in one place
│ ├── useKanaKanjiStore.ts
│ ├── useVocabStore.ts
│ ├── useStatsStore.ts
│ └── useThemeStore.ts
│
├── static/ # Unorganized static data
│ ├── kana.ts
│ ├── themes.ts
│ ├── fonts.ts
│ └── kanji/
│
├── lib/ # Uncategorized utilities
│ ├── hooks/
│ └── utils.ts
│
└── i18n/ # i18n separated, translations in root
- Separation by file type: Files were organized by their technical type (components, stores, data) rather than by functional purpose
- Cross-dependencies: A Kana component could import data from
static/, store fromstore/, hooks fromlib/hooks/ - Difficulty finding code: To work on a feature, you had to open multiple folders
- Confusing reusability: It wasn't clear what was feature-specific and what was shared
- Complicated testing: Testing a feature required navigating the entire structure
- No conventions: Each developer organized code their own way
The current architecture is based on three key principles:
- 🎯 Feature-First: Code is organized by functionality, not by type
- 📦 Encapsulation: Each feature is self-contained with everything it needs
- 🔄 Explicit Reusability: Shared code is clearly defined in
shared/
┌─────────────────────────────────────────────────────────┐
│ app/ (Next.js) │
│ Pages, Layouts, Routing │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ features/ (Modules) │
│ Self-contained and independent functionalities │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ shared/ (Shared) │
│ Reusable components, hooks, utilities │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ core/ (Infrastructure) │
│ i18n, Analytics, Base Configuration │
└─────────────────────────────────────────────────────────┘
kanadojo/
├── app/ # Next.js App Router
├── features/ # Feature modules
├── shared/ # Shared resources
├── core/ # Fundamental infrastructure
├── public/ # Static assets
└── docs/ # Documentation
Contains Next.js 15 pages, layouts, and routing. Uses the App Router pattern with internationalization.
app/
├── [locale]/ # Internationalized routes
│ ├── page.tsx # Home page (/)
│ ├── layout.tsx # Main layout
│ ├── loading.tsx # Loading state
│ ├── kana/ # Kana Dojo pages
│ │ ├── page.tsx # Kana selection
│ │ └── train/[gameMode]/page.tsx # Training
│ ├── kanji/ # Kanji Dojo pages
│ ├── vocabulary/ # Vocabulary Dojo pages
│ ├── academy/ # Academy pages
│ ├── achievements/ # Achievements page
│ ├── progress/ # Progress page
│ ├── preferences/ # Preferences settings
│ └── settings/ # General settings
├── layout.tsx # Root layout
├── globals.css # Global styles
└── ClientLayout.tsx # Client-side layout wrapper
Responsibilities:
- Routing and navigation
- Layouts and page structures
- Server components and data fetching
- Metadata and SEO
Rules:
- ❌ NO business logic in pages
- ✅ Pages only orchestrate features and shared components
- ✅ Use Server Components by default, Client Components when needed
- ✅ Import from
features/using barrel exports
Each feature is a self-contained module that groups all code related to a specific functionality.
features/
├── kana/ # Kana learning feature
│ ├── components/ # Kana-specific components
│ │ ├── KanaCards/ # Selection cards
│ │ ├── SubsetDictionary/ # Subset dictionary
│ │ └── Game/ # Training game
│ ├── data/ # Kana character data
│ │ └── kana.ts # Hiragana and Katakana
│ ├── lib/ # Kana-specific utilities
│ │ └── utils.ts # Kana helpers
│ ├── store/ # Kana state management
│ │ └── useKanaStore.ts # Zustand store
│ └── index.ts # Barrel export (public API)
│
├── kanji/ # Kanji learning feature
│ ├── components/
│ ├── store/
│ └── index.ts
│
├── vocabulary/ # Vocabulary learning feature
│ ├── components/
│ ├── store/
│ └── index.ts
│
├── statistics/ # Statistics and progress feature
│ ├── components/
│ │ ├── AchievementProgress/
│ │ ├── ProgressWithSidebar/
│ │ └── SimpleProgress/
│ ├── store/
│ │ └── useStatsStore.ts
│ └── index.ts
│
├── achievements/ # Achievement system
│ ├── lib/
│ │ ├── achievements.ts # Achievement definitions
│ │ ├── useAchievements.ts # Main hook
│ │ └── useAchievementTrigger.ts
│ ├── store/
│ │ └── useAchievementStore.ts
│ └── index.ts
│
├── themes/ # Themes and preferences feature
│ ├── components/
│ │ └── Settings/ # Settings panel
│ ├── data/
│ │ ├── themes.ts # 100+ themes
│ │ └── fonts.ts # 28 Japanese fonts
│ ├── store/
│ │ ├── usePreferencesStore.ts # General preferences
│ │ ├── useCustomThemeStore.ts # Custom themes
│ │ └── useGoalTimersStore.ts # Timers
│ └── index.ts
│
├── academy/ # Educational content feature
│ ├── components/
│ ├── data/
│ └── index.ts
│
└── cloze/ # Cloze test feature
├── data/
└── index.ts
Anatomy of a Feature:
// features/[feature-name]/
├── components/ # Specific components (OPTIONAL)
├── data/ # Data and constants (OPTIONAL)
├── lib/ # Utilities and helpers (OPTIONAL)
├── store/ # State management (OPTIONAL)
├── hooks/ # Custom hooks (OPTIONAL)
├── types/ # TypeScript types (OPTIONAL)
└── index.ts # Barrel export (REQUIRED)Rules for Features:
- ✅ Self-containment: A feature must contain EVERYTHING it needs to function
- ✅ Public API: Only expose what's necessary through
index.ts - ✅ Imports: Can only import from
shared/,core/, and other features (carefully) - ❌ No circular: Avoid circular dependencies between features
- ✅ Naming: Use descriptive and clear names for functionality
Example index.ts (Barrel Export):
// features/kana/index.ts
// Components (default exports)
export { default as KanaCards } from './components/KanaCards';
export { default as SubsetDictionary } from './components/SubsetDictionary';
export { default as KanaGame } from './components/Game';
// Store (default + named for compatibility)
export { default as useKanaStore } from './store/useKanaStore';
export { useKanaStore } from './store/useKanaStore';
// Data
export * from './data/kana';
// Utils
export * from './lib/utils';
// Types
export type { KanaCharacter, KanaGroup } from './data/kana';Reusable code across multiple features. Only truly shared code should be placed here.
shared/
├── components/ # Reusable components
│ ├── Game/ # Generic game component
│ ├── Modals/ # Modals (Welcome, Info, etc.)
│ ├── AudioButton.tsx # Audio button
│ ├── AchievementBadge.tsx
│ ├── Link.tsx # Link wrapper
│ └── ui/ # shadcn/ui components
│
├── hooks/ # Shared custom hooks
│ ├── useAudio.ts # Audio hook
│ ├── useKeyboard.ts # Keyboard shortcuts
│ └── useLocalStorage.ts # Persistence
│
├── lib/ # Shared utilities
│ ├── utils.ts # General functions
│ ├── cn.ts # Tailwind merge
│ └── unitSets.ts # JLPT unit sets
│
├── store/ # Shared stores
│ └── useOnboardingStore.ts # Onboarding state
│
└── types/ # Global TypeScript types
└── index.ts
Rules for Shared:
- ✅ Multiple usage: Only place code used by 2+ features
- ✅ No feature dependencies: Don't import from
features/ - ✅ Generic: Not specific to one functionality
- ❌ Not a dumping ground: Not for "homeless code"
Base project infrastructure that rarely changes.
core/
├── i18n/ # Internationalization
│ ├── config.ts # next-intl configuration
│ ├── routing.ts # Route configuration
│ ├── request.ts # Translation loading
│ └── locales/ # Translation files
│ ├── en.json # English
│ ├── es.json # Spanish
│ ├── fr.json # French
│ ├── de.json # German
│ ├── pt-br.json # Portuguese (Brazil)
│ ├── ar.json # Arabic
│ ├── ru.json # Russian
│ ├── zh-CN.json # Chinese (Simplified)
│ ├── zh-tw.json # Chinese (Traditional)
│ ├── hin.json # Hindi
│ ├── tr.json # Turkish
│ └── vi.json # Vietnamese
│
└── analytics/ # Analytics providers
├── GoogleAnalytics.tsx
└── MSClarity.tsx
Rules for Core:
- ✅ Fundamental: Only critical project infrastructure
- ✅ Stable: Code that rarely needs changes
- ❌ No business logic: Core is infrastructure, not features
Each module (features/, shared/) exposes its public API through an index.ts file.
Benefits:
- ✅ Clean imports:
import { KanaCards } from '@/features/kana' - ✅ Encapsulation: Only expose what's necessary
- ✅ Easy refactoring: Internal changes don't affect consumers
Example:
// ❌ BAD - Direct import
import KanaCards from '@/features/kana/components/KanaCards';
// ✅ GOOD - Barrel export
import { KanaCards } from '@/features/kana';Configured in tsconfig.json for clean imports:
{
"compilerOptions": {
"paths": {
"@/features/*": ["./features/*"],
"@/shared/*": ["./shared/*"],
"@/core/*": ["./core/*"],
"@/app/*": ["./app/*"]
}
}
}Usage:
// ✅ With path alias
import { useKanaStore } from '@/features/kana';
import { AudioButton } from '@/shared/components';
import { getTranslations } from '@/core/i18n';
// ❌ Without path alias (relative)
import { useKanaStore } from '../../../features/kana';Each feature can have its own Zustand store with localStorage persistence.
Store Structure:
// features/[feature]/store/use[Feature]Store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface FeatureState {
// State
data: SomeType[];
// Actions
setData: (data: SomeType[]) => void;
reset: () => void;
}
export const useFeatureStore = create<FeatureState>()(
persist(
set => ({
// Initial state
data: [],
// Actions
setData: data => set({ data }),
reset: () => set({ data: [] }),
}),
{
name: 'feature-storage', // localStorage key
},
),
);
export default useFeatureStore;Current Stores:
| Store | Feature | localStorage Key | Purpose |
|---|---|---|---|
useKanaStore |
kana | kana-storage |
Kana selection |
useKanjiStore |
kanji | kanji-storage |
Kanji selection |
useVocabStore |
vocabulary | vocab-storage |
Vocabulary selection |
useStatsStore |
statistics | stats-storage |
Statistics and progress |
useAchievementStore |
achievements | achievement-storage |
Achievement system |
usePreferencesStore |
themes | preferences-storage |
General preferences |
useCustomThemeStore |
themes | custom-theme-storage |
Custom themes |
useGoalTimersStore |
themes | goal-timers |
Goal timers |
useOnboardingStore |
shared/store | onboarding-storage |
Onboarding state |
General Rule:
- Components: Default export in file, named export in barrel
- Hooks/Utils: Named exports
- Stores: Default + named export (compatibility)
Example:
// features/kana/components/KanaCards/index.tsx
const KanaCards = () => {
/* ... */
};
export default KanaCards;
// features/kana/index.ts
export { default as KanaCards } from './components/KanaCards';
// Usage in page
import { KanaCards } from '@/features/kana';Problem:
// ❌ BAD - Circular dependency
// features/Preferences/data/themes.ts
import { useCustomThemeStore } from '@/features/Preferences'; // Barrel export
// features/Preferences/index.ts
export * from './data/themes'; // Exports themes.ts which imports from index.tsSolution:
// ✅ GOOD - Direct import
// features/themes/data/themes.ts
import { useCustomThemeStore } from '../store/useCustomThemeStore';Rule:
- Inside a feature, import directly from files
- Outside a feature, import from barrel export (
index.ts)
- Create folder structure:
mkdir -p features/new-feature/{components,data,lib,store}- Create barrel export:
// features/new-feature/index.ts
export { default as MainComponent } from './components/MainComponent';
export { default as useNewFeatureStore } from './store/useNewFeatureStore';
export * from './data/constants';- Implement store (if needed):
// features/new-feature/store/useNewFeatureStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface NewFeatureState {
// ...
}
export const useNewFeatureStore = create<NewFeatureState>()(
persist(
set => ({
// ...
}),
{
name: 'new-feature-storage',
},
),
);
export default useNewFeatureStore;- Create components:
// features/new-feature/components/MainComponent/index.tsx
import { useNewFeatureStore } from '../../store/useNewFeatureStore';
const MainComponent = () => {
// ...
};
export default MainComponent;- Use in page:
// app/[locale]/new-feature/page.tsx
import { MainComponent } from '@/features/new-feature';
export default function NewFeaturePage() {
return <MainComponent />;
}-
Determine if truly shared:
- Is it used by 2+ features?
- Is it generic and not feature-specific?
-
Create in
shared/components/:
// shared/components/NewComponent.tsx
export const NewComponent = () => {
// ...
};- Export in barrel:
// shared/components/index.ts
export { NewComponent } from './NewComponent';- Use:
import { NewComponent } from '@/shared/components';- Add key in all language files:
// core/i18n/locales/en.json
{
"newKey": "New translation"
}
// core/i18n/locales/es.json
{
"newKey": "Nueva traducción"
}- Use in component:
import { useTranslations } from 'next-intl';
const Component = () => {
const t = useTranslations();
return <p>{t('newKey')}</p>;
};If you encounter webpack errors about exports:
- Check the source file:
# See how it's actually exported
cat features/[feature]/components/Component.tsx- Check the barrel export:
# See what's exported in index.ts
cat features/[feature]/index.ts- Make sure they match:
- If component uses
export default, barrel must useexport { default as ... } - If it uses
export const, barrel must useexport *orexport { name }
# Build and see errors
npm run build
# Development mode with hot reload
npm run dev
# Linting
npm run lint
# Find direct imports (avoid)
grep -r "from '\.\./\.\./features" app/The migration of KanaDojo from an unstructured codebase to a feature-based architecture was a systematic process that involved:
- ✅ 8 features migrated: kana, kanji, vocabulary, statistics, achievements, themes, academy, cloze
- ✅ 100+ imports updated across the entire codebase
- ✅ 50+ webpack errors resolved related to exports
- ✅ Circular dependencies eliminated
- ✅ Barrel exports created for all modules
- ✅ Path aliases configured in TypeScript
- ✅ Stores reorganized by feature
- ✅ Documentation updated
| Old Location | New Location | Type |
|---|---|---|
components/Dojo/Kana/ |
features/kana/components/ |
Components |
components/Dojo/Kanji/ |
features/kanji/components/ |
Components |
components/Dojo/Vocab/ |
features/vocabulary/components/ |
Components |
components/Settings/ |
features/themes/components/Settings/ |
Components |
components/reusable/ |
shared/components/ |
Components |
store/useKanaKanjiStore.ts |
features/kana/store/useKanaStore.ts |
Store |
store/useKanaKanjiStore.ts |
features/kanji/store/useKanjiStore.ts |
Store |
store/useVocabStore.ts |
features/vocabulary/store/useVocabStore.ts |
Store |
store/useStatsStore.ts |
features/statistics/store/useStatsStore.ts |
Store |
store/useThemeStore.ts |
features/themes/store/usePreferencesStore.ts |
Store |
static/kana.ts |
features/kana/data/kana.ts |
Data |
static/themes.ts |
features/themes/data/themes.ts |
Data |
static/fonts.ts |
features/themes/data/fonts.ts |
Data |
lib/hooks/ |
shared/hooks/ |
Hooks |
lib/utils.ts |
shared/lib/utils.ts |
Utils |
lib/useOnboardingStore.ts |
shared/store/useOnboardingStore.ts |
Store |
i18n/ |
core/i18n/ |
Infrastructure |
translations/ |
core/i18n/locales/ |
Translations |
During migration, obsolete files were removed:
- ❌
update-imports.ps1- Temporary migration script - ❌
instrumentation-client.ts- Unused PostHog - ❌
tests/- Empty folder without tests - ❌ Old files in
components/Dojo/,components/Settings/,static/
- 📄
AI.md→docs/AI.md - 📄
CLAUDE.md→docs/CLAUDE.md - 📄
TRANSLATING.mdupdated with new structure
-
Circular dependency in Preferences.ts:
- Changed import from
@/features/Preferencesto../store/useCustomThemeStore
- Changed import from
-
Incorrect exports in barrels:
- Fixed 7+
index.tsfiles to match actual exports - Added missing component exports
- Added missing type exports
- Fixed 7+
-
Component paths:
- Fixed Game paths (from
Game/GametoGame) - Removed non-existent exports (hiragana, katakana)
- Fixed Game paths (from
-
localStorage keys:
- All keys preserved to maintain user data
- ✅ 0 TypeScript errors
- ✅ 0 webpack compilation errors
- ✅ 100% functional application
- ✅ User data preserved
- ✅ Better developer experience
- ✅ More maintainable and scalable code
KanaDojo's feature-based architecture represents a fundamental shift in how the project is organized and developed. What was once an unstructured codebase where developers worked "blindly" is now a modular, scalable, and easy-to-maintain system.
- 🎯 Guided development: Developers know exactly where to place new code
- 📦 Modularity: Each feature is independent and self-contained
- 🔍 Maintainability: Changes to one feature don't affect others
- 🚀 Scalability: Adding new features is simple and predictable
- 👥 Fast onboarding: New developers understand the structure immediately
- 🧪 Facilitated testing: Each feature can be tested independently
To continue improving the architecture:
- Testing: Implement unit and integration tests per feature
- Documentation: Keep docs updated with each new feature
- Performance: Lazy loading of features to optimize bundle size
- Storybook: Document shared components in Storybook
- CI/CD: Pipelines to verify structure in PRs
Last updated: January 2025 Architecture version: 2.0.0 (Hybrid Modular) Maintainers: LingDojo Team