Full Stack Project Structure with Separate Branches for Code and Static Builds
A simple Git workflow to separate code and static builds for clean full-stack deployments.
This guide walks you through a simple and long-lasting way to organize full-stack projects: one branch for source code and another for the generated static site.
This approach keeps development and deployment cleanly separated — perfect if you want to serve static builds on GitHub Pages, Netlify, Cloudflare, Vercel (static mode), or similar platforms.
The Idea in 30 Seconds
main(ordev) → contains the project’s code (frontend + backend).static→ contains the static build output generated by your frontend.- Use git worktrees to map each branch into its own directory inside the same repo.
Folder Structure
<bash>1/projectName2 /fullstack ← main branch (code)3 /frontend ← your UI app (exportable to static)4 /backend ← your API / CMS / headless service5 /static ← static branch (build artifacts)
Why put /static alongside /fullstack?
Because each directory is tied to a different branch in the same repo. That way, build artifacts don’t pollute your codebase, and the static site history lives independently.
Step by Step
1 - Create a remote repo
Create a new empty repository on GitHub (or any git hosting platform).
2 - Create directories
<bash>1mkdir -p projectName/fullstack/frontend projectName/fullstack/backend projectName/static2cd projectName/fullstack
3 - Initialize Git: code branch
<bash>1git init2git remote add origin https://github.com/YOUR_USERNAME/REPOSITORY_NAME.git3echo "# Project" > README.md4git add .5git commit -m "init: fullstack layout"6git branch -M main7git push -u origin main
IMPORTANT: replace YOUR_USERNAME and REPOSITORY_NAME
4 - Create the static branch
<bash>1git switch --orphan static2git commit --allow-empty -m "init: static branch"3git push -u origin static
5 - Link /static to the static branch with worktrees
<bash>1git switch main2git worktree add ../static static
Now you have:
/fullstack↔ main branch/static↔ static branch
Tip: git worktree list shows all active mappings.
Generic Workflow
- Develop in
/fullstack(branchmain). - Build your frontend (each framework has its own build command).
- Move the output (e.g.
out/,dist/,build/) into/static(branchstatic). - Commit + push to
static. Done — your static site is ready to deploy.
Example
Works with frameworks that export plain HTML: Next.js with next export, Astro, SvelteKit static, etc.
<bash>1# inside /fullstack/frontend2npm run build # generates 'out'34# move output into /static5cd ../../static6rm -rf out7mv ../fullstack/frontend/out .8git add .9git commit -m "release: new static build"10git push origin static
What About the Backend?
The backend (API, headless CMS, etc.) lives under /fullstack/backend and is versioned in main.
- If your frontend consumes the API at build time (SSG), the
staticbranch is self-contained: just rebuild when content changes. - If you need SSR or APIs at runtime, you can still use this setup for your static parts (docs, landing pages, blogs), while deploying the backend separately.
Best Practices
- .gitignore
- In
main: ignore build directories (node_modules,.next,dist, etc.). - In
static: do not ignore them — the build output is the content.
- In
- Environment variables
- Keep secrets outside the repo (Vault, Doppler, GitHub Secrets).
- Maintain a
.env.examplewith documented keys only.
- Commit discipline
main: messages about code changes.static: messages about releases/deploys.
- Rollback
- Resetting
staticto a previous commit instantly restores the published site.
- Resetting
Recap
- Use two branches: one for code (
main), one for static builds (static). - Map them to different directories with git worktree.
- Build frontend → move output to
/static→ commit + push. - Works with any framework that can export HTML or static files.