<?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: sainul ashiqu</title>
    <description>The latest articles on DEV Community by sainul ashiqu (@saitrogen).</description>
    <link>https://dev.to/saitrogen</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3784032%2F55cc3d6a-1b1b-4d8b-9d39-3e657e0ffda9.png</url>
      <title>DEV Community: sainul ashiqu</title>
      <link>https://dev.to/saitrogen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saitrogen"/>
    <language>en</language>
    <item>
      <title>System design as a macro skill for agents:Trade-off Knowledge</title>
      <dc:creator>sainul ashiqu</dc:creator>
      <pubDate>Sat, 11 Apr 2026 18:02:46 +0000</pubDate>
      <link>https://dev.to/saitrogen/system-design-as-a-macro-skill-for-agentstrade-off-knowledge-3jfj</link>
      <guid>https://dev.to/saitrogen/system-design-as-a-macro-skill-for-agentstrade-off-knowledge-3jfj</guid>
      <description>&lt;h2&gt;
  
  
  On system design as a macro skill, and why the gap matters more as agents get better
&lt;/h2&gt;

&lt;p&gt;There's a pattern I keep seeing. Someone hands a coding agent a project. The agent executes. The code works. Tests pass. And then, a few weeks later, things start quietly falling apart — not because of a bug, but because of a choice made early on. The wrong database for the access pattern. A queue where a simple job runner would've been fine. A microservice boundary that made sense in theory but turned the whole thing into a distributed monolith.&lt;/p&gt;

&lt;p&gt;The code was fine. The architecture wasn't.&lt;/p&gt;




&lt;p&gt;What a senior developer actually brings to a project isn't just coding ability. It's a kind of pre-loaded decision map. Before writing a single line, they already know which database fits this use case, which queue strategy makes sense at what scale, where the invisible ceilings are. Not because they looked it up — because they've already been through the moment where the thing broke and had to move off it.&lt;/p&gt;

&lt;p&gt;That's the gap. The agent is an excellent executor. The system design judgment is in the person directing it.&lt;/p&gt;

&lt;p&gt;When the person directing it &lt;em&gt;is&lt;/em&gt; a senior developer, things go well. They think it through, break it into clear tasks, hand it to the agent, and the agent fires through it. The hard work — what to build and with what — happened before the agent touched anything.&lt;/p&gt;

&lt;p&gt;But most people using agents right now aren't senior developers. And even developers who are solid coders sometimes haven't had enough breadth of production experience to know, say, when Cassandra becomes the wrong call, or why your MongoDB setup is going to start hurting at a specific scale.&lt;/p&gt;




&lt;p&gt;There's a distinction worth making here, though. If you're building something for yourself — no other users, no scaling concerns, just automating something from your own workflow — this gap barely matters. The reason is that what you're bringing to the table is something the agent genuinely can't replicate: deep domain knowledge of your own process. You know every step, every edge case, every place it breaks. That clarity is actually what makes the agent effective. You're the expert on the &lt;em&gt;what&lt;/em&gt;, they handle the &lt;em&gt;how&lt;/em&gt;. Agents are surprisingly capable in this mode.&lt;/p&gt;

&lt;p&gt;The problem starts the moment you need the thing to grow, or serve other people, or hold up under real load. That's system design territory. And that's where both the agent and the person hit the same wall.&lt;/p&gt;




&lt;p&gt;The obvious response is: just look it up. Read the docs. Search the trade-offs.&lt;/p&gt;

&lt;p&gt;The problem is that documentation is a reference, not a judgment. It tells you what something does. What it doesn't tell you is when not to use it, what it quietly fails at in production, what decision Discord made at 10M users that you'd be wise to borrow. That kind of knowledge lives in engineering post-mortems, conference talks, internal retrospectives, the kind of thing someone shares after getting burned.&lt;/p&gt;

&lt;p&gt;There are tools trying to address part of this — Context7 and similar MCP doc-fetchers are genuinely useful for one thing: you've already picked a tool, and you need the API right now. "How do I configure a Kafka consumer?" — they go get the docs. That's good. But it's solving a micro-level problem. The question it can't answer is: should you be using Kafka at all? That question doesn't live in any documentation page.&lt;/p&gt;




&lt;p&gt;Take Discord's message storage history as an example of the kind of judgment that matters.&lt;/p&gt;

&lt;p&gt;They started with MongoDB — flexible schema, fast to build on, fine at the beginning. Then message volume grew and the read/write patterns stopped fitting a document store. They moved to Cassandra — better suited for write-heavy workloads, scales linearly. But Cassandra brought its own problems: compaction-related latency spikes, and JVM garbage collection turning into an operational headache. They eventually moved to ScyllaDB — same API as Cassandra, rewritten in C++, GC issues gone, costs down.&lt;/p&gt;

&lt;p&gt;Three moves. Each one contains a real decision signal: when does MongoDB stop being the right call? What does Cassandra actually cost you, not in dollars but in operational complexity? When is the runtime itself the bottleneck?&lt;/p&gt;

&lt;p&gt;A senior developer carries all of that — not as a memorized list, but as pattern recognition. That's what makes them a good system designer. It's a macro skill, not a micro one. It's not "how to use Kafka." It's "when Kafka is wrong for this, and what it'll cost you to find out the hard way."&lt;/p&gt;




&lt;p&gt;This knowledge already exists. Most of it is scattered across engineering blogs, case studies, post-mortems, talks. The issue is that it's formatted for humans reading linearly — narratives, context, long explanations. That format makes sense for learning. It's terrible for retrieval at planning time.&lt;/p&gt;

&lt;p&gt;An agent in planning mode doesn't need to &lt;em&gt;learn&lt;/em&gt; system design. It just needs to &lt;em&gt;know the trade-off at the moment of decision&lt;/em&gt;. Those are two completely different information needs, and almost all the existing material is optimized for the first one.&lt;/p&gt;

&lt;p&gt;What the agent actually needs is something like: "Use Cassandra when you have write-heavy workloads that need horizontal scale. Don't use it when your team doesn't want to manage JVM tuning and compaction. If you're on this path, look at ScyllaDB." Terse. Judgment-first. Retrievable in two seconds.&lt;/p&gt;




&lt;p&gt;The structure that fits this best is something like an Obsidian vault. Not a flat list of tools — a graph. Notes organized by domain, connected through backlinks, so the agent can start with a vague requirement, follow the links, and arrive at a set of relevant trade-offs without having to read forty pages to get there. Each entry covers: what it is, when to use it, when not to, what it pairs with, what it silently costs you, and real examples. Short. Dense. Token-efficient.&lt;/p&gt;

&lt;p&gt;Some domains are self-contained — caching is mostly its own thing. Others bleed into each other — your database choice affects your queue strategy, which affects your deployment model. The backlinks capture that structure. The agent can navigate it the way a senior developer navigates their own mental model: start somewhere, follow the connections, arrive at the right place.&lt;/p&gt;

&lt;p&gt;I put together a starting scaffold for exactly this — the folder taxonomy, the entry template, a few seeded entries to show the format. It's not complete; it's nowhere near complete. But the shape is there. &lt;a href="https://github.com/saitrogen/system-design-skill-wiki" rel="noopener noreferrer"&gt;github.com/saitrogen/system-design-skill-wiki&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you've moved off a tool in production because it hit a wall — that decision is exactly what should be in here. Not the full story, just the signal: what you were building, what broke, what you moved to, and what you wish you'd known earlier.&lt;/p&gt;

&lt;p&gt;The goal isn't a new platform or a product. It's a structure that agents can actually use when they're planning, so the next person who hands a task to an agent doesn't end up with an architecture they'll regret in six months.&lt;/p&gt;

</description>
      <category>agentskills</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How I Fixed Next.js Deployments on Appwrite Sites with Turborepo (Monorepo)</title>
      <dc:creator>sainul ashiqu</dc:creator>
      <pubDate>Sat, 21 Feb 2026 16:24:38 +0000</pubDate>
      <link>https://dev.to/saitrogen/how-i-fixed-nextjs-deployments-on-appwrite-sites-with-turborepo-monorepo-3pld</link>
      <guid>https://dev.to/saitrogen/how-i-fixed-nextjs-deployments-on-appwrite-sites-with-turborepo-monorepo-3pld</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If your Appwrite Sites deployment builds successfully but gets stuck at "Finalizing" forever — the problem is that Appwrite's internal SSR bundler can't find your &lt;code&gt;next.config.js&lt;/code&gt; and &lt;code&gt;server.js&lt;/code&gt; at the monorepo root. Here's the exact fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;a href="https://mathsido.in" rel="noopener noreferrer"&gt;Mathsido&lt;/a&gt;, an ed-tech platform, using a &lt;strong&gt;Turborepo monorepo&lt;/strong&gt; with &lt;strong&gt;Next.js 16&lt;/strong&gt; and deploying to &lt;strong&gt;Appwrite Sites&lt;/strong&gt;. The folder structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mathsido/
├── apps/
│   ├── app/              ← Next.js app lives here
│   │   ├── next.config.js
│   │   ├── package.json
│   │   └── src/
│   └── landing/
├── packages/
│   ├── types/
│   ├── utils/
│   └── ui/
├── package.json          ← root workspace
├── pnpm-workspace.yaml
└── turbo.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I deployed to Appwrite Sites, the build would &lt;strong&gt;succeed perfectly&lt;/strong&gt; — all pages compiled, static pages generated, everything green. But then the deployment would get stuck on &lt;strong&gt;"Finalizing"&lt;/strong&gt; for hours, sometimes days, and the site would never become reachable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fxdg6yo0ww4uydou35v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fxdg6yo0ww4uydou35v.png" alt="Screenshot of Appwrite UI showing " width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Appwrite's Build Pipeline
&lt;/h2&gt;

&lt;p&gt;Before we fix the problem, let's understand what happens behind the scenes when Appwrite Sites deploys a Next.js app. This is key to understanding &lt;strong&gt;why&lt;/strong&gt; the build fails in a monorepo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Appwrite's Internal Build Steps
&lt;/h3&gt;

&lt;p&gt;Appwrite Sites uses &lt;a href="https://github.com/open-runtimes/open-runtimes" rel="noopener noreferrer"&gt;open-runtimes&lt;/a&gt; under the hood. When you trigger a deployment, it runs through these stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Environment Preparation     → Sets up Node.js, installs dependencies
2. Build Command Execution     → Runs YOUR build command (e.g., `pnpm build:app`)
3. SSR Bundling                → Appwrite's INTERNAL step (this is where it breaks)
4. Build Packaging             → Creates the deployment archive
5. Edge Distribution           → Deploys to Appwrite's edge network
6. Screenshot Capturing        → Takes a preview screenshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical step is &lt;strong&gt;Step 3: SSR Bundling&lt;/strong&gt;. This is NOT your code — it's Appwrite's internal script that prepares your Next.js app for their serverless runner.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Appwrite Expects (The Conventions)
&lt;/h3&gt;

&lt;p&gt;Here's where it gets interesting. During the SSR Bundling step, Appwrite's runner makes several &lt;strong&gt;assumptions&lt;/strong&gt; about your project structure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What Appwrite Looks For&lt;/th&gt;
&lt;th&gt;Expected Location&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;next.config.js&lt;/code&gt; / &lt;code&gt;next.config.mjs&lt;/code&gt; / &lt;code&gt;next.config.ts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Root of the build directory&lt;/strong&gt; (&lt;code&gt;/usr/local/build/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Appwrite runs &lt;code&gt;mv next.config.* .next/&lt;/code&gt; to bundle the config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.next/&lt;/code&gt; folder&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Root of the build directory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;This is where Next.js build output lives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.next/standalone/server.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Root of &lt;code&gt;.next/standalone/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Appwrite boots your app using this entry point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;public/&lt;/code&gt; folder&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Alongside &lt;code&gt;server.js&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static assets need to be accessible at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.next/static/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Inside &lt;code&gt;.next/&lt;/code&gt; next to &lt;code&gt;server.js&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client-side JS/CSS bundles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These conventions work perfectly for a &lt;strong&gt;standard single-project Next.js app&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-app/                        ← Appwrite builds from here
├── next.config.js             ✅ Found at root
├── public/                    ✅ Found at root
├── .next/
│   ├── static/                ✅ Found inside .next
│   └── standalone/
│       └── server.js          ✅ Found at standalone root
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  But in a Monorepo, Everything is Nested
&lt;/h3&gt;

&lt;p&gt;When you use Turborepo with &lt;code&gt;output: 'standalone'&lt;/code&gt; and &lt;code&gt;outputFileTracingRoot&lt;/code&gt; (which you &lt;strong&gt;must&lt;/strong&gt; set for monorepos), Next.js generates the standalone output &lt;strong&gt;relative to your monorepo root&lt;/strong&gt;, not the app directory.&lt;/p&gt;

&lt;p&gt;Here's what the build output actually looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/local/build/                  ← Appwrite's build root
├── next.config.js                 ❌ MISSING (it's in apps/app/)
├── .next/                         ❌ MISSING (it's in apps/app/.next/)
├── apps/
│   └── app/
│       ├── next.config.js         ← Appwrite can't find this
│       └── .next/
│           ├── static/
│           └── standalone/
│               ├── apps/
│               │   └── app/
│               │       └── server.js  ← Deeply nested!
│               └── node_modules/
├── package.json
└── turbo.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mismatch causes &lt;strong&gt;two cascading failures&lt;/strong&gt;:&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure #1: The &lt;code&gt;next.config.*&lt;/code&gt; Error
&lt;/h2&gt;

&lt;p&gt;During SSR Bundling, Appwrite runs something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; /usr/local/build/next.config.&lt;span class="k"&gt;*&lt;/span&gt; /usr/local/build/.next/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;next.config.js&lt;/code&gt; lives inside &lt;code&gt;apps/app/&lt;/code&gt;, not at the root, the &lt;code&gt;mv&lt;/code&gt; command fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mv: can't rename '/usr/local/build/next.config.*': No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbd0jkkwugymghckyzir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbd0jkkwugymghckyzir.png" alt="Screenshot of the build log showing the " width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In some cases, this error is &lt;strong&gt;non-fatal&lt;/strong&gt; and the build continues, but it's the first red flag.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure #2: The "Finalizing" Hang (The Silent Killer)
&lt;/h2&gt;

&lt;p&gt;Even if the build completes and packaging succeeds, Appwrite's runtime needs to find &lt;code&gt;server.js&lt;/code&gt; to boot your app. It looks for it at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.next/standalone/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in your Turborepo standalone output, &lt;code&gt;server.js&lt;/code&gt; is deeply nested at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.next/standalone/apps/app/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Appwrite can't find the entry point, fails to start the server, can't take a screenshot, and the deployment gets &lt;strong&gt;stuck in "Finalizing" indefinitely&lt;/strong&gt;. There's no error, no timeout — it just... hangs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixdl6l4fn4j9d0w81u6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixdl6l4fn4j9d0w81u6c.png" alt="Screenshot of Appwrite deployment stuck at " width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The solution is to add a &lt;strong&gt;post-build step&lt;/strong&gt; that restructures your output to match what Appwrite expects. We need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the &lt;code&gt;.next&lt;/code&gt; folder (with standalone output) to the monorepo root&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;next.config.js&lt;/code&gt; to the root&lt;/li&gt;
&lt;li&gt;Place &lt;code&gt;public/&lt;/code&gt; and &lt;code&gt;.next/static/&lt;/code&gt; where the runtime expects them&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;root-level &lt;code&gt;server.js&lt;/code&gt; wrapper&lt;/strong&gt; that delegates to the real nested server&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Update &lt;code&gt;next.config.js&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;First, make sure your Next.js config has these critical settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/app/next.config.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('next').NextConfig} */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;reactStrictMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Required for Appwrite SSR deployment&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Required for monorepo: tells Next.js to trace dependencies&lt;/span&gt;
    &lt;span class="c1"&gt;// starting from the monorepo root, not just this app's directory&lt;/span&gt;
    &lt;span class="na"&gt;outputFileTracingRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;// If you use node-appwrite SDK, mark it as external&lt;/span&gt;
    &lt;span class="na"&gt;serverExternalPackages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;// List your internal workspace packages&lt;/span&gt;
    &lt;span class="na"&gt;transpilePackages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mathsido/types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mathsido/utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ... your packages&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;output: 'standalone'&lt;/code&gt;?&lt;/strong&gt; This tells Next.js to create a self-contained server that includes only the files needed for production. Appwrite's SSR runner specifically looks for this output format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;outputFileTracingRoot&lt;/code&gt;?&lt;/strong&gt; In a monorepo, your app imports code from &lt;code&gt;packages/&lt;/code&gt;. Without this setting, Next.js only traces files inside &lt;code&gt;apps/app/&lt;/code&gt;, missing all your shared packages. Setting it to the monorepo root (&lt;code&gt;../../&lt;/code&gt; relative to &lt;code&gt;apps/app/&lt;/code&gt;) ensures everything is included.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create the Appwrite Build Script
&lt;/h3&gt;

&lt;p&gt;Add this script to your &lt;strong&gt;root&lt;/strong&gt; &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build:app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"turbo run build --filter=@mathsido/app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build:app:appwrite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm build:app &amp;amp;&amp;amp; rm -rf .next &amp;amp;&amp;amp; mkdir -p .next/standalone &amp;amp;&amp;amp; cp -r apps/app/.next/standalone/* .next/standalone/ &amp;amp;&amp;amp; cp -r apps/app/public .next/standalone/apps/app/public &amp;amp;&amp;amp; cp -r apps/app/.next/static .next/standalone/apps/app/.next/static &amp;amp;&amp;amp; echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;require('./apps/app/server.js')&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt; .next/standalone/server.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what this script does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Run the normal Turborepo build&lt;/span&gt;
pnpm build:app

&lt;span class="c"&gt;# 2. Clean any previous .next at root (avoid conflicts)&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; .next

&lt;span class="c"&gt;# 3. Create the directory structure Appwrite expects&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .next/standalone

&lt;span class="c"&gt;# 4. Copy the entire standalone output to root .next/standalone/&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; apps/app/.next/standalone/&lt;span class="k"&gt;*&lt;/span&gt; .next/standalone/

&lt;span class="c"&gt;# 5. Copy public assets to where the server expects them&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; apps/app/public .next/standalone/apps/app/public

&lt;span class="c"&gt;# 6. Copy static client bundles (JS/CSS) to the right place&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; apps/app/.next/static .next/standalone/apps/app/.next/static

&lt;span class="c"&gt;# 7. Create a root server.js wrapper that delegates to the real one&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"require('./apps/app/server.js')"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .next/standalone/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic is in step 7: we create a tiny &lt;code&gt;server.js&lt;/code&gt; at the root of &lt;code&gt;standalone/&lt;/code&gt; that simply requires the real server. When Appwrite runs &lt;code&gt;node server.js&lt;/code&gt;, it finds our wrapper, which boots the actual Next.js server from its nested location.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Configure Appwrite Build Settings
&lt;/h3&gt;

&lt;p&gt;In the Appwrite Console, go to your Site → Settings → Build, and update:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Install command&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pnpm install&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build command&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pnpm run build:app:appwrite&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output directory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.next/standalone&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rendering mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server side rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm84i8ke9e2e6e4k1wqt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm84i8ke9e2e6e4k1wqt1.png" alt="Screenshot of the Appwrite Build Settings UI with the correct values filled in" width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Deploy!
&lt;/h3&gt;

&lt;p&gt;Push your changes and trigger a new deployment. You should now see the &lt;strong&gt;complete&lt;/strong&gt; build pipeline succeed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[open-runtimes] Environment preparation started.
[open-runtimes] Environment preparation finished.
[open-runtimes] Build command execution started.
...
✓ Compiled successfully
✓ Generating static pages (17/17)
...
[open-runtimes] Bundling for SSR started.
[open-runtimes] Bundling for SSR finished.      ← No more errors!
[open-runtimes] Build packaging started.
[open-runtimes] Build packaging finished.
[appwrite] Edge distribution started.
[appwrite] Edge distribution finished (6/6).    ← Success!
[appwrite] Deployment finished.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop2g5eo5jci4m74oyhk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop2g5eo5jci4m74oyhk5.png" alt="" width="800" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1lqo6o22cw76vx28xps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1lqo6o22cw76vx28xps.png" alt="Screenshot of the successful deployment log in Appwrite showing all stages complete" width="522" height="240"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Picture
&lt;/h2&gt;

&lt;p&gt;Here's a visual summary of the problem and the fix:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before (Broken)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Appwrite looks for:              What Turborepo generates:
─────────────────                ────────────────────────
/usr/local/build/                /usr/local/build/
├── next.config.js    ❌ MISS    ├── apps/app/next.config.js
├── .next/                       ├── apps/app/.next/
│   └── standalone/              │   └── standalone/
│       └── server.js ❌ MISS    │       └── apps/app/server.js
└── public/           ❌ MISS    └── apps/app/public/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After (Fixed)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/local/build/
├── next.config.js              ← Copied from apps/app/
├── .next/                      ← Copied from apps/app/.next/
│   └── standalone/
│       ├── server.js           ← Wrapper: require('./apps/app/server.js')
│       ├── apps/
│       │   └── app/
│       │       ├── server.js   ← Real Next.js server
│       │       ├── public/     ← Static assets
│       │       └── .next/
│       │           └── static/ ← Client bundles
│       └── node_modules/
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Bonus: Fixing the Broken Lockfile Warning
&lt;/h2&gt;

&lt;p&gt;You might also notice this warning in your build logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARN  Ignoring broken lockfile: duplicated mapping key (1032:3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happens when &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; has duplicate entries. To fix it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Regenerate a clean lockfile&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;pnpm-lock.yaml
pnpm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is benign — pnpm will skip the broken lockfile and resolve fresh — but it adds ~10-15 seconds to every build. Fixing it once saves time on every deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Appwrite Sites makes assumptions&lt;/strong&gt; about where Next.js files live. These work fine for single-project repos but break with monorepos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;next.config.*&lt;/code&gt; must exist at the build root.&lt;/strong&gt; During SSR bundling, Appwrite runs &lt;code&gt;mv next.config.* .next/&lt;/code&gt; from the root directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;server.js&lt;/code&gt; must be at &lt;code&gt;.next/standalone/server.js&lt;/code&gt;.&lt;/strong&gt; This is the entry point Appwrite's Node.js runner uses to boot your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The fix is a post-build restructuring script&lt;/strong&gt; that copies outputs to where Appwrite expects them and creates a tiny server wrapper.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;outputFileTracingRoot&lt;/code&gt; is mandatory for monorepos.&lt;/strong&gt; Without it, Next.js won't include your shared packages in the standalone build.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Environment &amp;amp; Versions
&lt;/h2&gt;

&lt;p&gt;For reference, here's the exact stack this was tested with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;td&gt;16.1.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turborepo&lt;/td&gt;
&lt;td&gt;2.8.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pnpm&lt;/td&gt;
&lt;td&gt;10.0.0 / 9.15.9 (Appwrite)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;22.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Appwrite Sites&lt;/td&gt;
&lt;td&gt;Cloud (Feb 2026)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Useful Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://appwrite.io/docs/products/sites" rel="noopener noreferrer"&gt;Appwrite Sites Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/output" rel="noopener noreferrer"&gt;Next.js Standalone Output&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://turbo.build/repo/docs" rel="noopener noreferrer"&gt;Turborepo Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/open-runtimes/open-runtimes" rel="noopener noreferrer"&gt;open-runtimes GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If this helped you, share it with someone else stuck on the same issue. Happy deploying! 🚀&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#nextjs&lt;/code&gt; &lt;code&gt;#appwrite&lt;/code&gt; &lt;code&gt;#turborepo&lt;/code&gt; &lt;code&gt;#monorepo&lt;/code&gt; &lt;code&gt;#deployment&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#typescript&lt;/code&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>turborepo</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
