<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohamed DJOUDIR</title>
    <description>The latest articles on DEV Community by Mohamed DJOUDIR (@mohamed_djoudir).</description>
    <link>https://dev.to/mohamed_djoudir</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3171599%2Fad32db11-ab91-4174-8d8e-55e47e72a4e7.png</url>
      <title>DEV Community: Mohamed DJOUDIR</title>
      <link>https://dev.to/mohamed_djoudir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohamed_djoudir"/>
    <language>en</language>
    <item>
      <title>I built a full-stack Next.js 16 e-commerce monorepo: storefront, admin, NestJS API, and AI try-on</title>
      <dc:creator>Mohamed DJOUDIR</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:08:11 +0000</pubDate>
      <link>https://dev.to/mohamed_djoudir/i-built-a-full-stack-nextjs-16-e-commerce-monorepo-storefront-admin-nestjs-api-and-ai-try-on-2kbb</link>
      <guid>https://dev.to/mohamed_djoudir/i-built-a-full-stack-nextjs-16-e-commerce-monorepo-storefront-admin-nestjs-api-and-ai-try-on-2kbb</guid>
      <description>&lt;p&gt;I shipped a production e-commerce system as a single monorepo: a Next.js 16 storefront, a Next.js 16 admin dashboard, and a NestJS API. English and Arabic with full RTL, guest checkout, and three AI features that live inside the codebase instead of being bolted on top.&lt;/p&gt;

&lt;p&gt;This is the build log. The decisions I would defend, the ones that cost me a week, and what I would do differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three apps, not one
&lt;/h2&gt;

&lt;p&gt;The obvious move is one Next.js app with &lt;code&gt;/admin&lt;/code&gt; routes behind a guard. I split it into three.&lt;/p&gt;

&lt;p&gt;The storefront and the admin have almost nothing in common at the boundary. The storefront is public, SEO-critical, and cached aggressively. The admin is authenticated on every request, data-heavy, and never indexed. Sharing a build means the storefront pays for Recharts, the data grids, and the admin's client bundle even when nobody signs in.&lt;/p&gt;

&lt;p&gt;Splitting also made auth honest. Customers and staff are different tables with different token payloads and different refresh lifetimes. When they share an app you inevitably end up with one &lt;code&gt;User&lt;/code&gt; model carrying an &lt;code&gt;isAdmin&lt;/code&gt; boolean, and RBAC on top of a boolean is how privilege escalation bugs happen.&lt;/p&gt;

&lt;p&gt;Cost of the split: three ports in dev, a shared types package, and CORS config you have to actually think about. Worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  RTL is a layout problem, not a translation problem
&lt;/h2&gt;

&lt;p&gt;Arabic support is where most templates quietly fail. Translating strings is the easy half. The hard half is that every hardcoded &lt;code&gt;margin-left&lt;/code&gt;, &lt;code&gt;left-0&lt;/code&gt;, &lt;code&gt;flex-row&lt;/code&gt;, and chevron icon is now wrong.&lt;/p&gt;

&lt;p&gt;The rule that fixed it: no physical direction properties anywhere. Logical properties only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* breaks in RTL */&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* works in both */&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;padding-inline-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailwind 4 makes this workable because &lt;code&gt;ms-*&lt;/code&gt;, &lt;code&gt;me-*&lt;/code&gt;, &lt;code&gt;ps-*&lt;/code&gt;, &lt;code&gt;pe-*&lt;/code&gt;, &lt;code&gt;start-*&lt;/code&gt;, and &lt;code&gt;end-*&lt;/code&gt; are first class. Once I banned &lt;code&gt;ml-&lt;/code&gt;, &lt;code&gt;mr-&lt;/code&gt;, &lt;code&gt;pl-&lt;/code&gt;, &lt;code&gt;pr-&lt;/code&gt;, &lt;code&gt;left-&lt;/code&gt;, and &lt;code&gt;right-&lt;/code&gt; from the codebase, the same components served both directions with no mirrored variants.&lt;/p&gt;

&lt;p&gt;The leftovers that still bite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Icons with implied direction. A "next" chevron has to flip. A logo does not. There is no automatic rule, you tag them by hand.&lt;/li&gt;
&lt;li&gt;Carousels. Embla needs its direction option set from the locale or drag goes the wrong way.&lt;/li&gt;
&lt;li&gt;Numbers and currency. &lt;code&gt;Intl.NumberFormat&lt;/code&gt; with the right locale, not string concatenation.&lt;/li&gt;
&lt;li&gt;Transforms. &lt;code&gt;translateX&lt;/code&gt; does not mirror. Animations needed direction-aware values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For messages I used next-intl with one file per feature per locale rather than one giant &lt;code&gt;en.json&lt;/code&gt;. A 2000-line translation file is unreviewable and guarantees merge conflicts. &lt;code&gt;checkout.en.json&lt;/code&gt; next to &lt;code&gt;checkout.ar.json&lt;/code&gt; means a translator touches one file and you can diff it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guest checkout: the order is the identity
&lt;/h2&gt;

&lt;p&gt;Forcing account creation before purchase kills conversion, so guest checkout was non-negotiable. It changes the data model more than you would expect.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;order.userId&lt;/code&gt; is non-nullable, every guest order needs a phantom user, and now you have a users table full of ghosts that break your customer analytics. Instead the order owns its own contact and shipping snapshot, and &lt;code&gt;userId&lt;/code&gt; is nullable.&lt;/p&gt;

&lt;p&gt;Snapshot is the key word. The shipping address on an order is a copy, not a foreign key to an address row. When a customer edits their saved address six months later, historical orders must not silently change. Same for line items: product name, variant, and unit price get denormalized onto the order line at purchase time. Prices change. Invoices should not.&lt;/p&gt;

&lt;p&gt;Guest order tracking is then just order number plus the email used at checkout. No account, no magic link infrastructure, and the lookup is rate-limited so it is not an enumeration oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual try-on
&lt;/h2&gt;

&lt;p&gt;A shopper taps a hanger icon on any product page and sees the garment worn, either on a built-in model or on their own uploaded photo.&lt;/p&gt;

&lt;p&gt;The architecture that made it feel fast:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The upload goes straight to S3-compatible storage with a presigned URL. The API never proxies the image bytes.&lt;/li&gt;
&lt;li&gt;Generation is a job, not a request. The client gets a job id immediately and subscribes over Socket.io.&lt;/li&gt;
&lt;li&gt;Results are cached per garment plus model pair, so the same product on the built-in model is generated once, ever. That is most of the traffic.&lt;/li&gt;
&lt;li&gt;Per-account quotas, enforced server-side. Image generation has real unit cost, and an unmetered endpoint is a bill waiting to happen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The piece I underestimated: composition. Shoppers do not want one item, they want an outfit. Building up a look piece by piece meant persisting the try-on session state so the next garment composites onto the previous result instead of starting from a blank model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI studio lives inside the product editor
&lt;/h2&gt;

&lt;p&gt;Merchants do not want a separate AI tool. They want the product form to fill itself.&lt;/p&gt;

&lt;p&gt;So the generation UI sits in the product editor: generate product imagery from a prompt, remove backgrounds, draft the listing copy from a single uploaded photo, and turn a still into a short product video with motion presets.&lt;/p&gt;

&lt;p&gt;The detail that changed usage more than the model choice: three one-tap prompt suggestions sitting directly above the composer. "Write the product details." "Clean white studio shot." "Photograph this on a model." Tap, then Send. A blank prompt box gets ignored. Three buttons get pressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The admin assistant queries the database, not the internet
&lt;/h2&gt;

&lt;p&gt;The dashboard assistant is a streaming chat, but the interesting part is that it answers from store data through an MCP tool registry rather than from general knowledge.&lt;/p&gt;

&lt;p&gt;Each tool is a typed, scoped function: revenue for a period, orders awaiting fulfilment, top sellers, low stock. The model picks tools and composes the answer. It cannot free-form SQL, so it cannot leak or wreck anything the tool contract does not allow.&lt;/p&gt;

&lt;p&gt;Two things fell out of this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provider choice is trivial. Anthropic, Google, and OpenAI all speak tools, so the provider is config. Bring your own key.&lt;/li&gt;
&lt;li&gt;Answers are auditable. Every claim traces to a tool call with real numbers, not a plausible hallucinated figure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suggested questions on the empty state again, for the same reason as the studio prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time without a state mess
&lt;/h2&gt;

&lt;p&gt;New orders, status changes, and notifications push over Socket.io. The temptation is to write incoming events into a client store and treat that store as the source of truth. That path ends in two copies of your data that disagree.&lt;/p&gt;

&lt;p&gt;What worked: the socket event carries an identifier, not a payload. The handler invalidates the relevant TanStack Query key and the normal fetch path refills it. One source of truth, no reconciliation logic, and a page that recovers correctly after a reconnect.&lt;/p&gt;

&lt;p&gt;Redux Toolkit stays for genuinely client-owned state: cart, wishlist, UI preferences. Server state never enters it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring things that mattered most
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A one-command seeder.&lt;/strong&gt; Products, variants, images, customers, orders across every status. Nothing kills first-run enthusiasm like an empty database and a broken chart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact pinned dependencies with committed lockfiles.&lt;/strong&gt; No carets. A template that installs a different tree next month is a support ticket generator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branding in one config file per app.&lt;/strong&gt; Colors, logo, store name. If a rebrand means grepping for hex codes, the theming is fake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No flash of the wrong theme.&lt;/strong&gt; A blocking inline script reads the stored preference before paint. Tiny detail, immediately noticeable when missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;I would design the RTL rule on day one instead of retrofitting it. Auditing every physical property after the fact took a week of tedious work that a lint rule would have prevented from the start.&lt;/p&gt;

&lt;p&gt;I would also make the AI features quota-gated from the first commit rather than adding limits later. Retrofitting metering into an existing generation flow is more invasive than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The whole thing is available as a template if you would rather start from a working store than a blank &lt;code&gt;create-next-app&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.aniq-ui.com/en/templates/next-js-ecommerce-template" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aniq-ui.com/en/templates" rel="noopener noreferrer"&gt;Template details and pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stack: Next.js 16 App Router, React 19, TypeScript 5, Tailwind CSS 4, TanStack Query, Redux Toolkit, React Hook Form and Zod, next-intl, NestJS, TypeORM with MySQL or SQLite, Socket.io.&lt;/p&gt;

&lt;p&gt;Happy to go deeper on any part of this in the comments. The RTL and the try-on caching are the two I have the most notes on.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I Built My Own Next.js Template Store (And What I Learned)</title>
      <dc:creator>Mohamed DJOUDIR</dc:creator>
      <pubDate>Fri, 10 Oct 2025 17:07:11 +0000</pubDate>
      <link>https://dev.to/mohamed_djoudir/how-i-built-my-own-nextjs-template-store-and-what-i-learned-53la</link>
      <guid>https://dev.to/mohamed_djoudir/how-i-built-my-own-nextjs-template-store-and-what-i-learned-53la</guid>
      <description>&lt;p&gt;A few months ago, I decided to build my own Next.js landing page template store : &lt;a href="https://www.aniq-ui.com/en" rel="noopener noreferrer"&gt;https://www.aniq-ui.com/en&lt;/a&gt;.&lt;br&gt;
I wanted a place to sell templates I designed and coded myself, but I also wanted it to reflect my taste in UI and performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I Chose Next.js ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve worked with React for years, and Next.js gave me the balance between speed, SEO, and developer control.&lt;br&gt;
Its app router and server components made it easy to scale templates cleanly without messy state handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Next.js for structure and SSR&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TailwindCSS for consistent design&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TypeScript for safety and scalability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Framer Motion for subtle motion design&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple local JSON for i18n, later upgrading to a CMS&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Learned&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Designing for reusability is harder than it looks.&lt;/p&gt;

&lt;p&gt;A fast page is nice, but good UX sells.&lt;/p&gt;

&lt;p&gt;You don’t need a team, just focus, taste, and iteration.&lt;/p&gt;

&lt;p&gt;🌍 Check It Out&lt;/p&gt;

&lt;p&gt;You can see the result here: &lt;a href="https://www.aniq-ui.com/en/templates" rel="noopener noreferrer"&gt;https://www.aniq-ui.com/en/templates&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>website</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
