Private Git Submodules with Vercel
How to set up Vercel to clone your private Git repo submodules when installing & building your app.

For the content of this website, I use a private Git submodule within my public timmyomahony-portfolio
repo. This lets me keep the content of the website separate to the codebase which is a nice way to stay organised.
One of the drawbacks is having to deal with submodules, particularly when deploying and building my site via Vercel.
You need to configure Vercel to clone the submodule during the build, which is tricky as it’s not natively supported and requires an access token to be able to read private repos.
There are a few resources already out there, but they didn’t work for me, so here’s how I got it working.
Setting Up Submodules
First, make sure .gitmodules
is in your .gitignore
and not committed to the parent repo. This is an important step, as if there’s a .gitmodules
already present in the repo when deploying it can confuse Git.
For Local Development
When working locally, I already have SSH access to Github on my machine and therefore just need to run the following to set up the Git submodule:
git clone --recurse-submodules git@github.com:timmyomahony/timmyomahony-content.git content
This clones the private repo into the ./content
folder and adds a .gitmodules
that looks like:
[submodule "content"]
path = content
url = git@github.com:timmyomahony/timmyomahony-content.git
Now I can push and pull to the private timmyomahony-content
as normal:
cd content
git commit
git push origin main
For Vercel Deployment
In order for Vercel to be able to clone the private submodule during build-time, we need to configure the submodule using HTTPS + an access token.
Create Access Token
First, go to Github and set up a “fine grained personal access token”:
A few notes on the process:
- Make sure it doesn’t expire.
- Make sure it only has access to your content repo and not all your repos.
- Only grant it “read access to code and metadata”.
- Jot it down as you won’t be shown it again.
Add Environment Variable
Now go over to your Vercel project and add this token as an environment variable GITHUB_REPO_CLONE_TOKEN
:
Create Submodule Script
Create a new script in your parent repo bin/init-submodule.sh
and chmod +x
it. Add the following script:
#!/usr/bin/env bash
set -euo pipefail
# Move to the repo root
cd "$(git rev-parse --show-toplevel)"
# Ensure we’re not in a detached HEAD or bare repo
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "Not inside a valid Git working tree."
exit 1
fi
# Remove existing submodule entry (if any)
if git config --file .gitmodules --get-regexp "^submodule\.content\." > /dev/null 2>&1; then
echo "Removing existing submodule config..."
git submodule deinit -f content || true
git rm -f content || true
rm -rf .git/modules/content
fi
# Clean local content dir if needed
rm -rf content
# Add the submodule
echo "Adding submodule..."
git submodule add -f "https://timmyomahony:${GITHUB_REPO_CLONE_TOKEN}@github.com/timmyomahony/timmyomahony-content.git" content
# Sync & init
git submodule sync
git submodule update --init --recursive
This script configures the git submodule on every build, pulling down the private timmyomahony-content
repo.
Add NPM Script
Update your package.json
to include an install-script that will pull the submodule:
"scripts": {
"install:vercel": "bash ./bin/init-submodule.sh && npm install",
// ...
},
Configure Vercel Install Script
Finally, in your Vercel project, configure the install step:
Now every time your project is built, your submodule will be cloned and available from your parent website.