<?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>Forem: sainul ashiqu</title>
    <description>The latest articles on Forem by sainul ashiqu (@saitrogen).</description>
    <link>https://forem.com/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>Forem: sainul ashiqu</title>
      <link>https://forem.com/saitrogen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/saitrogen"/>
    <language>en</language>
    <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://forem.com/saitrogen/how-i-fixed-nextjs-deployments-on-appwrite-sites-with-turborepo-monorepo-3pld</link>
      <guid>https://forem.com/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>
