Skip to content

Latest commit

 

History

History
785 lines (610 loc) · 25.5 KB

File metadata and controls

785 lines (610 loc) · 25.5 KB

🏗️ KanaDojo Architecture

📋 Table of Contents


Introduction

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.

Why This Document?

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.


Previous State

Unstructured Organization (Before)

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

Identified Problems

  1. Separation by file type: Files were organized by their technical type (components, stores, data) rather than by functional purpose
  2. Cross-dependencies: A Kana component could import data from static/, store from store/, hooks from lib/hooks/
  3. Difficulty finding code: To work on a feature, you had to open multiple folders
  4. Confusing reusability: It wasn't clear what was feature-specific and what was shared
  5. Complicated testing: Testing a feature required navigating the entire structure
  6. No conventions: Each developer organized code their own way

Current Architecture

Fundamental Principles

The current architecture is based on three key principles:

  1. 🎯 Feature-First: Code is organized by functionality, not by type
  2. 📦 Encapsulation: Each feature is self-contained with everything it needs
  3. 🔄 Explicit Reusability: Shared code is clearly defined in shared/

Architecture Layers

┌─────────────────────────────────────────────────────────┐
│                     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             │
└─────────────────────────────────────────────────────────┘

Directory Structure

Overview

kanadojo/
├── app/                    # Next.js App Router
├── features/               # Feature modules
├── shared/                 # Shared resources
├── core/                   # Fundamental infrastructure
├── public/                 # Static assets
└── docs/                   # Documentation

📁 app/ - Next.js App Router

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

📦 features/ - Feature Modules

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:

  1. Self-containment: A feature must contain EVERYTHING it needs to function
  2. Public API: Only expose what's necessary through index.ts
  3. Imports: Can only import from shared/, core/, and other features (carefully)
  4. No circular: Avoid circular dependencies between features
  5. 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';

🔗 shared/ - Shared Resources

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:

  1. Multiple usage: Only place code used by 2+ features
  2. No feature dependencies: Don't import from features/
  3. Generic: Not specific to one functionality
  4. Not a dumping ground: Not for "homeless code"

🧱 core/ - Fundamental Infrastructure

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:

  1. Fundamental: Only critical project infrastructure
  2. Stable: Code that rarely needs changes
  3. No business logic: Core is infrastructure, not features

Patterns and Conventions

1. Barrel Exports Pattern

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';

2. TypeScript Path Aliases

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';

3. State Management with Zustand

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

4. Components: Default vs Named Exports

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';

5. Preventing Circular Dependencies

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.ts

Solution:

// ✅ 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)

Developer Guide

Adding a New Feature

  1. Create folder structure:
mkdir -p features/new-feature/{components,data,lib,store}
  1. 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';
  1. 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;
  1. Create components:
// features/new-feature/components/MainComponent/index.tsx
import { useNewFeatureStore } from '../../store/useNewFeatureStore';

const MainComponent = () => {
  // ...
};

export default MainComponent;
  1. Use in page:
// app/[locale]/new-feature/page.tsx
import { MainComponent } from '@/features/new-feature';

export default function NewFeaturePage() {
  return <MainComponent />;
}

Adding a Shared Component

  1. Determine if truly shared:

    • Is it used by 2+ features?
    • Is it generic and not feature-specific?
  2. Create in shared/components/:

// shared/components/NewComponent.tsx
export const NewComponent = () => {
  // ...
};
  1. Export in barrel:
// shared/components/index.ts
export { NewComponent } from './NewComponent';
  1. Use:
import { NewComponent } from '@/shared/components';

Adding a New Translation

  1. Add key in all language files:
// core/i18n/locales/en.json
{
  "newKey": "New translation"
}

// core/i18n/locales/es.json
{
  "newKey": "Nueva traducción"
}
  1. Use in component:
import { useTranslations } from 'next-intl';

const Component = () => {
  const t = useTranslations();

  return <p>{t('newKey')}</p>;
};

Debugging Imports

If you encounter webpack errors about exports:

  1. Check the source file:
# See how it's actually exported
cat features/[feature]/components/Component.tsx
  1. Check the barrel export:
# See what's exported in index.ts
cat features/[feature]/index.ts
  1. Make sure they match:
  • If component uses export default, barrel must use export { default as ... }
  • If it uses export const, barrel must use export * or export { name }

Development Tools

# 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/

Migration Details

Summary of Transformation

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

Migration Mapping

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

Files Removed

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/

Files Reorganized

  • 📄 AI.mddocs/AI.md
  • 📄 CLAUDE.mddocs/CLAUDE.md
  • 📄 TRANSLATING.md updated with new structure

Critical Fixes

  1. Circular dependency in Preferences.ts:

    • Changed import from @/features/Preferences to ../store/useCustomThemeStore
  2. Incorrect exports in barrels:

    • Fixed 7+ index.ts files to match actual exports
    • Added missing component exports
    • Added missing type exports
  3. Component paths:

    • Fixed Game paths (from Game/Game to Game)
    • Removed non-existent exports (hiragana, katakana)
  4. localStorage keys:

    • All keys preserved to maintain user data

Results

  • 0 TypeScript errors
  • 0 webpack compilation errors
  • 100% functional application
  • User data preserved
  • Better developer experience
  • More maintainable and scalable code

Conclusion

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.

Achieved Benefits

  • 🎯 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

Next Steps

To continue improving the architecture:

  1. Testing: Implement unit and integration tests per feature
  2. Documentation: Keep docs updated with each new feature
  3. Performance: Lazy loading of features to optimize bundle size
  4. Storybook: Document shared components in Storybook
  5. CI/CD: Pipelines to verify structure in PRs

Last updated: January 2025 Architecture version: 2.0.0 (Hybrid Modular) Maintainers: LingDojo Team