Private Content with Git Submodules
When building sites with personal content — journal entries, portfolio projects, work history — you face a dilemma: you want the code public on GitHub, but the content private. Here's how I solved it with Git submodules.
The Problem
Both my projects (react-playground and digital-handshake) use MDX files for
content. A build script copies these files into src/app/ before Next.js builds
the static site. Initially, real content was simply gitignored — it lived on my
machine and nowhere else.
This had two issues:
- No version history for real content
- Running the build script deleted entries that only existed in
src/app/but not in the source directories — a nasty bug when the content folders were empty
The Solution: Private Submodules
The idea is simple: keep example content in the public repo as a fallback, and store real content in a separate private repository added as a Git submodule.
Structure
project/
├── journal-examples/ # public repo (tracked)
│ └── example-entry/
├── journal/ # private submodule
│ ├── real-entry-one/
│ └── real-entry-two/
├── scripts/
│ └── prepare-content.mjs # copies entries into src/app/
└── src/app/journal/
├── page.tsx # list page (tracked)
└── */ # entries (generated, gitignored)
The Build Script
The prepare-content.mjs script uses a fallback strategy:
- If the private submodule (
journal/) is cloned and has content → use it - Otherwise → fall back to
journal-examples/ - The
--examplesflag forces the use of examples
if (forceExamples) {
sourceDir = EXAMPLES_DIR
} else if (hasEntries(JOURNAL_DIR)) {
sourceDir = JOURNAL_DIR
} else {
sourceDir = EXAMPLES_DIR
}
The generated entries in src/app/journal/*/ are gitignored since they're
recreated on every npm run dev or npm run build.
New Machine Workflow
Setting up on a fresh machine:
# Option 1: Clone with submodules in one go
git clone --recurse-submodules git@github.com:user/react-playground.git
# Option 2: If already cloned without submodules
git submodule update --init
After that, npm run dev automatically picks up the real content from the
submodule.
Without access to the private repo, the site still builds with the example entries — useful for contributors or CI previews.
Key Takeaway
Git submodules give you the best of both worlds: a public repo that anyone can clone and build, with private content that's versioned, backed up, and automatically included when you have access. The fallback to examples means the project always works, even without the private data.