Private Git Submodules with Vercel

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

Private Git Submodules with Vercel
Pawel Czerwinski

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”:

Screenshot of Github

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:

Screenshot of Vercel

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:

Updated Vercel project dashboard

Now every time your project is built, your submodule will be cloned and available from your parent website.