Deploy to Netlify
Connect Netlify, trigger a build of your GitHub repo, and ship your site live. The build command, publish directory, the Supabase env vars MaShop sets for you, and the per-plan deploy quota.
How-to guide.
This guide takes your project from a connected repo to a live Netlify site. Netlify is the only deployment target MaShop wires. Vercel is not a target you deploy to from the workspace; the deploy picker was reduced to Netlify only.
Before you start
A deploy builds the GitHub repo MaShop committed your code to, then serves the build on Netlify. Three accounts must be connected first, all under your own credentials:
- GitHub holds the source. MaShop pushes the generated code here, and Netlify pulls from it.
- Supabase holds your database. The deploy reads its URL and keys to inject them as build env vars.
- Netlify runs the build and serves the site.
You own all three. MaShop never proxies your traffic or stores your customer data. For the wider ownership model, see Your code and Deploy overview.
Open the deploy flow
In the workspace, click the publish button in the top nav. Its label is Publish before your first go-live and Update once the project has been deployed once.
What happens next depends on connection state:
- If any of GitHub, Supabase, or Netlify is not connected, the Deploy your project modal opens to the connect checklist.
- If all three are connected but the project was never deployed, the modal opens to run the first deploy and show the live link.
- If all three are connected and the project was already deployed, MaShop redeploys directly with no modal, surfacing a toast (Updating your site, then Site updated with a View site action).
Step 1: connect Netlify (and GitHub and Supabase)
In the modal, the Connect your services section lists three cards. The Netlify card reads Free deployment, global CDN, automatic SSL and, once linked, Ready to deploy. Click Connect on any card to open an OAuth popup. Authorize MaShop, and the popup closes back to the modal with that service marked connected.
The Publish button stays disabled until all three are connected. The checklist polls /api/oauth/check-connections to read the live state, so connecting a service in another tab also unlocks the button.
The first go-live needs provisioning, not only a build
If the modal shows a First go-live? banner, your Supabase project and GitHub repo have not been created yet. That banner runs the provisioning flow (create your Supabase project, create the mashop-{slug} GitHub repo, push the code, trigger the first build). The plain deploy endpoint only redeploys an already provisioned project.
Step 2: trigger the deploy
Click Publish (or Update). The workspace POSTs to /api/deploy-netlify with your project_id. That endpoint, through deployProjectToNetlify, does the following:
- Reserves a deploy slot against your plan quota (see Step 5).
- Loads your project row and refuses if it is
deletedorsuspended. - Reads your Netlify OAuth token. If none exists, it returns
NETLIFY_NOT_CONNECTEDwith the message Netlify non connecte. Connectez votre compte Netlify d'abord. - If the project has no Netlify site yet, it generates a Netlify deploy key, adds that key to your GitHub repo as read-only, then creates a Netlify site linked to the repo on branch
main(or yourgithub_branch). The site name is derived from your project slug asmashop-{slug}, lowercased. - If a site already exists (
projects.netlify_site_idis set), it reuses it. - Pushes your Supabase env vars to the site (Step 4).
- POSTs an explicit build to
/sites/{siteId}/builds.
The endpoint returns as soon as the build is triggered. It writes deployments.status = 'building' and project_deployment.status = 'deploying', and never writes a success state itself.
GitHub must be linked first
If your project has no linked GitHub repo, the deploy stops with GITHUB_NOT_LINKED: GitHub repo non lie. Connecte GitHub et cree un projet d'abord. Netlify pulls source from the repo, so there is nothing to build without it. See GitHub.
Step 3: the build command and publish directory
MaShop creates the Netlify site with this build configuration:
| Setting | Value |
|---|---|
| Build command | pnpm install --ignore-workspace && pnpm build |
| Publish directory | .next |
| Branch | main (or your project's branch) |
The generated repo also carries a netlify.toml at its root that restates this exact command and publish directory, pins NODE_VERSION = "20", and includes the @netlify/plugin-nextjs plugin. Netlify reads netlify.toml first, so the committed file is the canonical config; the site-creation command is belt and braces.
[build]
command = "pnpm install --ignore-workspace && pnpm build"
publish = ".next"
[build.environment]
NODE_VERSION = "20"
Why pnpm, not npm
The code uses pnpm because the template ships packageManager: pnpm@9.15.4 and a pnpm lockfile. --ignore-workspace is required because your repo holds the app on its own with no parent monorepo; without it pnpm walks upward looking for a workspace file and fails. The command was switched from npm install && npm run build after npm hit peer-dependency conflicts on React 19 and Radix UI against a pnpm-resolved tree.
Step 4: the env vars MaShop sets on your site
When your project has a Supabase URL, anon key, and service key recorded, the deploy pushes three environment variables to your Netlify site, scoped to all deploy contexts. These let your built site read its own database directly:
| Variable | Source | Exposure |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Your Supabase project URL | Public (browser-readable) |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Your Supabase anon key | Public (browser-readable, RLS-restricted) |
SUPABASE_SERVICE_ROLE_KEY | Your Supabase service-role key (decrypted from storage) | Server-only |
MaShop sets only these three. Any other env var your site needs (a payment provider key, an email API key, anything else) you add yourself in the Netlify site settings. The merchant chooses which payment provider to wire; nothing in the env push assumes one.
If the env push is skipped
If the service-role key cannot be decrypted, or Netlify does not return an account slug for the site, the deploy logs the failure and continues without pushing env vars. Your build then runs without Supabase credentials and your pages that read the database will fail at request time. Re-run the deploy after confirming Supabase is connected.
Step 5: the deploy quota per plan
Deploying is free for every plan because it runs on your own Netlify and Supabase. The Free plan is rate-limited; paid plans are not.
| Plan | Deploy quota | On exceeding |
|---|---|---|
| Free | 3 per day | HTTP 402 with a toast: the day's deploy limit is reached, retry in 24h or upgrade |
| Pro | Unlimited | Never blocked by quota |
| Max | Unlimited | Never blocked by quota |
The check runs in the reserve_deploy_slot database function, which combines the count check and the increment in a single locked transaction so two near-simultaneous deploys cannot both slip past a limit of 3. The Free window is a rolling 1 day that resets lazily on the next deploy after it elapses. When the limit is hit, the response is HTTP 402; the workspace treats 402 as non-transient, shows the toast, and does not arm a retry. See Plans for what each tier includes.
There is a separate per-IP rate limit of 5 deploy requests per 10 minutes. Exceeding it returns HTTP 429 with Trop de deploiements. Reessayez dans quelques minutes.
Step 6: watch the build finish
After triggering, the deploy endpoint runs a short server-side poll (a budget of about 20 seconds) against Netlify. If the build reaches a terminal state in that window, the endpoint returns status: 'live' or status: 'failed' directly. If the build is still running, it returns status: 'building' and the client takes over.
In the modal, a five-step progress bar advances through Verif, Supabase, GitHub, Netlify, and Termine, polling /api/deployment/status every 3 seconds. On success it shows Your site is live at: with the URL, plus Copy and Open buttons. The live build log streams into the workspace bottom-dock Logs tab, and a View logs button is available on the success screen.
Finalization is authoritative: MaShop confirms Netlify reports the deploy state ready, then makes an HTTP request to the deployed URL and only flips the status to live when that probe returns a 2xx or 3xx. If Netlify reports ready but the probe fails, the deploy is marked failed.
A failed build leaves the previous version serving
When a build errors, Netlify keeps your last successful deploy live. MaShop records the failure on the new attempt, restores project_deployment to the previous successful URL, and surfaces it so your domain keeps serving the working version while you fix the build.
Redeploys
Every redeploy reuses the existing Netlify site and repushes the env vars before triggering a fresh build. From the second deploy on, with all services connected, the nav button redeploys silently and reports progress through a toast rather than the modal. Each redeploy still consumes one slot of the Free 3-per-day quota.
Common failures and what they mean
| Symptom | Cause | Fix |
|---|---|---|
NETLIFY_NOT_CONNECTED | No Netlify OAuth token | Connect Netlify in the deploy modal |
GITHUB_NOT_LINKED | No GitHub repo on the project | Connect GitHub and create the project first |
| HTTP 402 with a quota toast | Free plan hit 3 deploys today | Wait for the daily reset or upgrade to Pro/Max |
| HTTP 429 | More than 5 deploy requests in 10 minutes from your IP | Wait a few minutes and retry |
| Build runs, pages fail at runtime | Supabase env vars were not pushed | Confirm Supabase is connected, then redeploy |
Next steps
- Point a domain at the site: Custom domain.
- Understand the source MaShop pushes: GitHub.
- See where the database lives: Supabase.
