Combining Clean Architecture + Feature-Based in React — does it really fix the earlier trade-offs, or am I missing new pitfalls?

Combining Clean Architecture + Feature-Based in React — does it really fix the earlier trade-offs, or am I missing new pitfalls?

Hi everyone. I compared four ways to structure a React project by rebuilding the same app (posts CRUD against an open API) in each one. The last pattern combines Clean Architecture with Feature-Based, and I'd really appreciate a sanity check from more experienced devs.

Here's the progression I went through, and the problem I felt at each step:

  • Feature-Based (colocate everything for a feature in one folder): great for navigation and deletion, but nothing controls how features depend on each other (circular deps creep in), shared/ turns into a junk drawer, and there's no notion of layers. (Feature-Based write-up)
  • FSD (Feature-Sliced Design): fixes that with standardized layers + a one-way import rule, so circular deps become structurally impossible. But the business logic still lives inside React/TanStack Query — the entity's api layer imports axios and react-query directly. (FSD write-up)
  • Clean Architecture: pulls business logic out of the framework with the Dependency Rule (dependencies point only inward; the domain knows nothing about React or axios). Great for testing and reuse — but now the code for "one feature" is scattered across domain/, infrastructure/, presentation/. Which is ironically the same "scattered by type" problem Feature-Based tried to solve. (Clean Architecture write-up)
  • The combination: keep the Dependency Rule (domain is pure TS, infrastructure holds the adapters), but colocate the UI (hooks + components) by feature in features/{feature}/. "Clean inside, Feature outside."

Rough shape:

src/
  domain/{domain}/        # pure TS: entities, rules, use cases (no framework imports)
  infrastructure/         # adapters: repository impls, query keys, stores
  features/{feature}/     # hooks + components, colocated
  pages/ , router/        # composition only
  shared/ , providers/

A few extra decisions I made: split the repository interface into Commands/Queries (CQS), write a UseCase only when there's real logic (plain CRUD calls the repository directly), and lean on React Compiler so there's no manual useMemo/useCallback.

What I'd love feedback on:

  1. Does this combination actually solve the earlier patterns' problems, or does it just move them around? Is "Clean inside + Feature outside" a real improvement over plain FSD or plain Clean, or is it over-engineering in disguise?
  2. What problems does this pattern itself have that I might not see yet? Boilerplate, the domain <-> infrastructure indirection, the "is this a UseCase or a direct repo call?" judgment, testing overhead, onboarding cost — where does it bite in real projects?

Honest criticism is very welcome. I'd rather hear "this is overkill for most apps" now than after I build on it.

Full write-up (with all the code) on Medium (Free): https://medium.com/@inkweonkim/react-architecture-combining-clean-architecture-feature-based-92cf7ba226fe

(English isn't my first language, so I apologize in advance for any awkward phrasing — happy to clarify anything that reads strangely.)

u/inkweon — 16 hours ago
▲ 49 r/Habits

Everyone says start with one habit. I started three at once and I'm on day 45.

Every habit guide I've read says the same thing: start with one. Add another only when the first one is automatic. I did the opposite — I started three on the same day — and today is day 45 without a break on all three.

I don't think I got lucky. I think the "one at a time" rule is really a rule about size, and people apply it to count.

The three:

  • A glass of water before work
  • Write for the blog
  • Workout at the park

On paper that's a lot. In practice it's maybe 25 minutes total, and two of them are under two minutes.

Two things I did differently this time.

1. I made them small enough to be embarrassing. Not "drink 2L" — one glass. Not "write a post" — one paragraph. Not "train" — just get to the park. The bar is low enough that there is no version of a bad day where I can't clear it. I've had days where the blog habit was literally one bad sentence I deleted the next morning. It still counted, and that's the point: I wasn't protecting the quality, I was protecting the chain.

2. I attached each one to something I already do, not to a time. This is the part I'd underlined in every book and never actually done.

  • Water: turn off the alarm → walk to the kitchen → fill a glass before coffee
  • Blog: finish dinner → open the laptop → write one paragraph
  • Park: change clothes → grab the water bottle → out the door

"After I turn off my alarm" is unmissable. "At 7am" is a negotiation I lose. The only one with a clock on it is the park workout at 11pm, and that's not motivation — it's just when I'm free. Everything else hangs off an action.

The screenshots are where I track it — the first is today, the second is July with the whole month filled in.

Forty-five days without a miss isn't a streak I've earned the right to be smug about. It's just one that hasn't been tested yet.

u/inkweon — 11 days ago

[Feedback wanted] Local-only habit tracker (Android) — just shipped a big Today-screen rework, looking for people to poke holes in it

I build a local-only habit tracker (no account, no server, data never leaves the device — Android, free with one banner ad). I just shipped the biggest update since launch and I'd like outside testers before I keep going.

What changed:

  • Habit cards now show your cues as an ordered vertical rail ending at the check circle, instead of a flat list. Long lists fold to first + last step.
  • New Timeline view: your day laid out as morning / afternoon / evening / anytime, toggled from the same header.
  • Home-screen widget: shows your streak and today's habits, and you can check them off without opening the app. Resizable to 2×2, and you choose which habits each widget shows.
  • Share cards: exports a 1080×1080 image of your streak and today's checklist.

What I'd specifically like torn apart:

  1. The app's premise is that time doesn't matter and continuing does — but I just added a time-ordered view and it's now the most-used one. Is that a good read of reality or am I abandoning my own idea?
  2. Does the cue rail read as a sequence at a glance, or does it just look decorative?
  3. If you can check habits off from the widget, is there any reason left to open the app? I genuinely don't know if that's a win.
  4. First-run: creating a habit is 3 steps (goal → cues → reminder). Too many, too few?

I made this. Android only, iOS isn't out — it's free on the Play Store: https://play.google.com/store/apps/details?id=io.inkweon.smallhabit

u/inkweon — 11 days ago

Updated the habit tracker I made for myself — the habit cards finally look like how the habit actually works

A while back I made a small habit tracker for myself. No account, no server, everything on the phone. The rule of the app is that a habit isn't attached to a time, it's attached to small cues you already do — coffee, sitting at the desk, that kind of thing.

The problem was those cues rendered as a plain bullet list, and a bullet list doesn't feel like a sequence. So I rebuilt the card: the cues now run down a rail and the rail ends at the check circle. Same information, but now the card looks like the path you walk to get to the check. It's a small thing and it changed how I use my own app, which surprised me.

While I was in there I added a home-screen widget you can tick habits off from without opening the app, and a view that lays the day out as a timeline instead of a list.

I made this, and I'm mostly posting because getting a UI to finally match the idea behind it is a pretty rare feeling. Screenshots attached. Keeping the store link out of the body; happy to drop it in a comment.

Android only for now: https://play.google.com/store/apps/details?id=io.inkweon.smallhabit (or search "Kept habit tracker" on the Play Store). Happy to answer anything about how it's built.

u/inkweon — 11 days ago

The Vite React template ships with React Compiler OFF — how to turn it on and verify it

If you scaffold a project with pnpm create vite@latest ... --template react-ts and assume React Compiler is on — it isn't. The template ships with it OFF, and I kept meeting people (myself included) who never actually enabled it. Here's the whole setup, start to finish.

  1. Install the compiler's Babel plugin + the Vite wiring:
pnpm install -D babel-plugin-react-compiler@latest @rolldown/plugin-babel
  1. Add the preset in vite.config.ts:
import { defineConfig } from 'vite';
import react, { reactCompilerPreset } from '@vitejs/plugin-react';
import babel from '@rolldown/plugin-babel';

export default defineConfig({
  plugins: [
    react(),
    babel({ presets: [reactCompilerPreset()] }),
  ],
});

Heads up: @vitejs/plugin-react 6.0 removed the old inline react({ babel: { plugins: [...] } }) form. A lot of guides still show it — it no longer works. Restart the dev server after editing the config.

  1. Verify it's actually running. Open React DevTools → Components tab. Optimized components get a "Memo ✨" badge next to their name. See the badge and the compiler is doing its job — with zero hand-written useMemo/useCallback.

A few related things that surprised me setting this up in 2026:

  • The newest Vite template lints with Oxlint, not ESLint. Installing eslint-plugin-react-hooks does nothing there unless you add your own ESLint config.
  • StrictMode rendering your component twice in dev is intentional (a purity check), not a bug.
  • Node 25 dropped Corepack, so how you get pnpm depends on your Node version.

I wrote the full beginner walkthrough — every command + these version traps — as chapter 1 of a series (React 19 + Compiler, TypeScript from line one).

Full write-up (free): https://medium.com/javascript-in-plain-english/setting-up-your-react-dev-environment-your-first-app-with-vite-and-turning-on-react-compiler-5436ea44d6d4?sharedUserId=inkweonkim

Happy to answer setup questions in the comments. And if you're already running the compiler in prod, curious how it's gone for you.

u/inkweon — 26 days ago

I built a tiny local habit tracker because every other one made me feel behind

I made a small Android app to track my own habits and figured this was the right place to show it.

Backstory: I could never stick with habits, and most tracker apps I tried made it worse — big streak numbers I'd break and then abandon, accounts, sync, notifications nagging me. So I built a stripped-down one for myself around two ideas that actually work for me:

  • Cues over clock. Instead of "do X at 9pm," you chain a habit to small triggers you already do, in order. Mine are literally "11pm walk → little park workout" and "open laptop → browser → Medium → write." The signal does the work, not willpower.
  • Continuity over intensity. The main number is just "are you keeping it going," not how long or how hard. One missed day isn't a failure, it just nudges you to not miss twice.

It's fully local — no account, no server, nothing leaves your phone (I didn't want my habit data anywhere but my own device). Free, one small banner ad, that's it. Screenshots are from my own phone with my real streaks.

Would genuinely love feedback on whether the "cue chain" idea reads clearly to someone seeing it fresh, since it's the whole point of the thing.

Google Play Store : https://play.google.com/store/apps/details?id=io.inkweon.smallhabit

u/inkweon — 1 month ago
▲ 0 r/Habits

The cue that made daily writing stick for me was physical, not a time of day

I spent a long time trying to build a "write every day" habit and failing, and I think I finally get why.

I kept framing it as a scheduling problem — "I'll write at 9pm" — and time-based cues just don't work on me. 9pm comes and I'm doing something else and the intention evaporates. There's no physical trigger, just a number on a clock.

What flipped it was making the cue a small chain of actions I already do: open the laptop → open the browser → go to the site I write on. That sequence itself is now the signal to write. I'm not waiting for a time or for motivation; by the time the page is open, my brain already knows what happens next. The environment does the reminding, not my willpower.

And I dropped the bar hard. The rule isn't "write a good post," it's "write something." Some days it's a paragraph. Keeping the run going matters more to me than the quality of any single day, and weirdly the quality got better once I stopped demanding it upfront.

reddit.com
u/inkweon — 1 month ago

Day 47. The trick that finally worked was attaching the workout to a walk I already do

I've tried to "start working out" more times than I can count and it never survived the first week. Every version was some big plan — gym membership, a 5-day split, morning runs. All of it died by day 4 because I was relying on motivation to do a hard new thing from scratch.

What actually stuck this time is embarrassingly small. I already go for a walk around 11pm most nights to clear my head. So instead of inventing a brand new "workout time," I just bolted a few minutes of exercise onto the end of that walk. The walk is the cue. By the time I'm already outside and moving, doing a little more isn't a decision anymore, it's just the next step.

Some nights it's a proper little session at the park near me. Some nights, honestly, it's like two minutes and I go home. I stopped caring about the size of it. The only rule I gave myself is I don't skip two nights in a row — one miss is a life thing, two in a row is the habit dying. That single rule saved me more than any streak pressure ever did.

Longest I've ever kept anything physical.

reddit.com
u/inkweon — 1 month ago
▲ 1 r/claude+1 crossposts

Solo-dev side project (mobile-web mini-game) — Claude wrote most of the code, now I'm stuck on assets

👋 Quick heads-up before we start: I'm a Korean solo dev and my English isn't great, so I used Claude to help me write this post. Please bear with me if anything reads a bit off — happy to clarify in the comments.

What I'm building

A mobile-web baseball pitching mini-game. Stop a moving white bar inside the green zone to throw a strike. 3 strikes = stage clear, 4 balls = game over. Random "conditions" (hangover, stomach ache, flu, gambling debt, personal life crisis) modify each pitch's difficulty.

It's a tiny side project I've been hacking on between work. Claude Code wrote something like 90%+ of the codebase — React 19 + Vite 7 + Tailwind v4 + Framer Motion + Zustand. I drive, Claude types.

What I just shipped (the revamp)

  1. Design system rewrite — tokens, motion presets, a unified ScreenRoot / ScreenStage layout shared across all 8 screens. Consolidated my old comic + announcement screens into one variant-driven ResultCinematic.
  2. New 5-condition system replacing the old pressure/weather pair — hangover 🍺, stomach 🤢, flu 🤒, gambling 🃏, personal 🖼️. Each has 2–3 gameplay effects (jitter, visual noise, speed-up, zone shrink, delayed feedback).
  3. Korean + English i18n with a Zustand-persisted toggle in the top-right.
  4. PC gauge stutter fixed — caching track width via ResizeObserver, dropping the button drop-shadow animation during gameplay, capping content to 480px on desktop.
  5. Ad banner + Ko-fi tip jar + analytics (GA4 + Clarity) so the project can stay free without breaking gameplay clicks.

A few Claude-specific bits worth mentioning:

  • I set up a small agent/skill harness under .claude/agents/ and .claude/skills/ — a design-auditor (Explore) → design-fixerdesign-qa pipeline. This actually caught most of the layout inconsistencies during the revamp. Way better than one big "review everything" prompt.
  • Used plan mode heavily so I could course-correct before any code got written.
  • The Opus 4.7 (1M context) window made it feasible to throw the whole src/screens/ tree plus design tokens at it in one shot.

Where I'm stuck — asset creation with Claude

The game still uses inline SVG and system fonts, and public/sounds/ is empty (Howler.js is installed, just nothing to play). I'd love community advice on:

  1. Image/sprite assets — has anyone used Claude (canvas-design skill, artifacts, or just direct prompting) to produce game-ready PNG/SVG that doesn't scream "AI default"? Any workflow tips?
  2. Sound effects — short SFX like a strike chime, ball whoosh, UI clicks. Is there a Claude-friendly pipeline (prompt → some audio tool → wav)? Or do people just outsource this to dedicated tools (Suno, ElevenLabs, freesound)?
  3. Custom fonts — open to free Google Fonts, but curious if anyone designs custom display fonts with Claude in the loop.

Even pointers to good threads/posts would help a ton.

Tech stack (for the curious)

React 19 (strict) · Vite 7 · Tailwind v4 · Framer Motion (LazyMotion) · Zustand · Howler.js (idle) · react-ga4 + Clarity · Cloudflare Pages

🎮 Play: https://baseball-game-8wx.pages.dev/

Thanks for reading — and apologies again if my English wanders. 🙇

u/inkweon — 3 months ago