Deploy overview
How shipping a store works from the workspace: provision your Supabase project, GitHub repo, and code, then deploy to Netlify. Learn the difference between publishing and redeploying, and why you own the result.
Explanation: how a store goes from the workspace to a live site.
Shipping a store happens in two distinct moves. The first move, Publish, provisions infrastructure: it creates your own Supabase project, creates your own GitHub repository, pushes the code into it, and triggers the first Netlify build. The second move, Deploy, redeploys an already provisioned project: it rebuilds the same Netlify site from the same repository. This page explains both, in order, with the real stages and limits.
Netlify is the only deployment target. The workspace removed the old Netlify/Vercel picker; Vercel is not a deployment target you can pick.
You own the result
Every artifact lands in your accounts. The GitHub repository is created under your GitHub login. The Supabase project is created under your Supabase organization. The Netlify site builds and serves from your Netlify account, using your OAuth token. MaShop holds the builder, not your live site or your customer data.
Because the deploy runs on your own Netlify and your own Supabase, MaShop pays nothing per deploy. That is why deploying is free on every plan, subject to a daily count described below.
The three accounts you connect
Before you can ship, connect three services in the Deploy modal. The "Deploy" button stays disabled until all three are connected.
- GitHub hosts the code in your own repository.
- Supabase provides the PostgreSQL database, Auth, and Storage.
- Netlify builds and serves the site over a global CDN with automatic SSL.
Each connector opens an OAuth popup. The modal re-checks connection state when the popup closes and when it receives an oauth-callback message from the same origin.
Publish: the first go-live
Publishing is the resumable provisioning flow. In the Deploy modal, when a project has never been provisioned, a banner labeled "Premiere mise en ligne ?" opens the Publish modal. The Publish modal drives a state machine one step at a time, polling POST /api/projects/[projectId]/provision every 3 seconds and showing per-stage progress.
Publishing is owner-only and requires a paid plan. The provision route returns 402 with code UPGRADE_REQUIRED and the message "La publication necessite un plan payant." for accounts that are not on Pro or Max (super admins excepted). Only the project owner can trigger it, because it creates real infrastructure on your accounts.
The provisioning stages, in order
The state machine advances through these stages, each persisted to projects.provisioning_state so a retry resumes instead of starting over:
| Stage | What happens |
|---|---|
supabase_creating | Creates a Supabase project named mashop-{slug} in your first Supabase organization. |
supabase_waiting_ready | Polls the project status every 4 seconds until it reports ACTIVE_HEALTHY. |
supabase_applying_migrations | Applies the bundled backend migrations to your new database, one at a time, recording each as it lands so a retry skips what already ran. The modal shows a running migration count. |
github_creating_repo | Creates a private repository named mashop-{slug} under your GitHub account, unless you already linked one. |
github_pushing_template | Pushes the backend code into the repository as a single commit. The modal shows the file count. |
netlify_triggering_build | Creates the Netlify site linked to your repository and triggers the first build. |
ready | Provisioning finished; the deployed URL is surfaced. |
The repository starts initialized (README, LICENSE,.gitignore), then one follow-up commit copies the full backoffice tree on top in a single Git tree operation, so the whole codebase lands in roughly three API calls regardless of size.
Why it polls in short steps
Provisioning takes 30 to 90 seconds end to end. Each call to the provision route runs a budget-bounded step of about 50 seconds, then saves state and returns outcome: 'pending' so the client polls again. This keeps any single request inside the serverless timeout. Each stage is idempotent: a Supabase project is created only when no project ref is recorded, and the template push is skipped once a commit SHA is recorded, so a mid-flight retry never double-creates a project or pushes the code twice.
If publishing fails
The Publish modal maps raw infrastructure errors to plain guidance. An expired Supabase connection surfaces a reconnect prompt: the provision runner returns 412 with "Reconnectez Supabase pour publier." when the Supabase OAuth token cannot be refreshed. Retryable failures clear and resume the state machine from where it stopped.
Deploy: redeploy an already provisioned project
Once a project is provisioned, the Deploy modal's main button rebuilds the existing site. The button reads "Publier" the first time and "Update" on later deploys. It posts to POST /api/deploy-netlify, the only merchant-facing deploy endpoint.
Deploy access is owner or editor; viewers are blocked because deploying mutates your Netlify account. The route reuses the existing Netlify site when projects.netlify_site_id is set, otherwise it provisions a new site linked to your repository via a read-only deploy key. It then pushes your Supabase URL, anon key, and service-role key to the site as Netlify environment variables so the deployed app reads from your own database.
The build runs on Netlify with this command and publish directory:
pnpm install --ignore-workspace && pnpm build
# publish directory:.next
How the status settles
The deploy route triggers the build and writes deployments.status = 'building' and project_deployment.status = 'deploying'. It never writes a successful state directly. A short bounded poll runs server-side right after the trigger; if the build is not yet terminal, the Deploy modal keeps polling GET /api/deployment/status every 3 seconds. The status route asks Netlify for the real deploy state and finalizes the row to live or failed. The modal's five-cell pipeline (Verif, Supabase, GitHub, Netlify, Termine) reflects that status as it moves.
The deploy limit
Deploying is free on every plan. The Free plan is capped at 3 deploys per day; Pro and Max are unlimited. The cap is enforced atomically in a single locked transaction by the reserve_deploy_slot RPC, so concurrent deploys cannot slip past it.
When the Free cap is reached, the deploy route returns 402 and the modal shows a toast: "Limite de 3 deploiements par jour atteinte. Reessaie dans 24h ou passe a un plan superieur." A 402 is treated as non-transient, so the modal closes instead of arming its retry loop. Retryable build errors get up to 3 retries from the modal.
First builds do not count
The first Netlify build triggered during publishing does not count against the daily Free quota; it is provisioned as a courtesy. Subsequent deploys count normally.
Publish versus deploy, side by side
| Publish (provision) | Deploy (redeploy) | |
|---|---|---|
| Route | POST /api/projects/[projectId]/provision | POST /api/deploy-netlify |
| Who | Project owner only | Owner or editor |
| Plan | Pro or Max required (402 otherwise) | Free, capped at 3 per day on Free |
| Creates Supabase project | Yes | No |
| Creates GitHub repo and pushes code | Yes | No |
| Triggers Netlify build | Yes (first build) | Yes (rebuild) |
After it ships
On success the Deploy modal shows the live URL with copy and open actions. When the platform has an SSO signing key configured (the NEXT_PUBLIC_MASHOP_SSO_ENABLED flag set to 1), an "Ouvrir mon tableau de bord" button hands you into the deployed backoffice; when the flag is unset, that button is hidden.
For the deploy-target details, see Netlify deploys. To attach your own domain, see Custom domain. To work with the repository MaShop created for you, see GitHub repository and Your Supabase. Plan limits are detailed in Plans.
