Vite, Vercel Serverless, and Client-Side Routing

Getting vercel dev to work with vite locally so serverless functions and client-side routing play nicely.

Vercel Serverless Functions are an an indispensable tool for building apps on Vercel, and the only way to test them locally is with the CLI.

vercel dev works perfectly with Next.js and create-react-app, but recently I came across a strange bug when using vercel dev with my vite application.

Let’s walk through the bug and then walkthrough the solution.

The Bug

Imagine you have a vite app with client-side routing and a few routes deployed to Vercel, like this one.

Navigating with the links works great, but if you try to load a page other than the index Vercel gets confused. Try going directly to the about page.

There’s no file at /about.html, so instead we see an error from vercel.

Vercel error

This is a common stumbling block for client-side routing. To fix it, we need to make sure any requests to our domain are redirected to /index.html. That way our application will load, which in turn loads our router, which in turn loads the correct page.

To do this on vercel we use a vercel.json file with the following contents:

{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}

If we deploy this, we can now load our pages directly. Try it out!

Vercel Dev

Now, instead of running your app locally with yarn dev, try running vercel dev.

You’ll need to have have the Vercel CLI installed. The first time you run vercel dev you’ll need to connect it to your deployment.

When you see something like this in your console you’ll know it’s running as intended.

Vercel dev running vite

However, when you visit the address on localhost, you’ll see our app is broken! 😱

What’s the problem?

Using vercel.json to fix routing on our deploys breaks our routing locally. I reached out to Vercel about this problem. They confirmed it’s an issue and they also said it wasn’t on their roadmap to fix it.

But I love vite too much to give up!

The Quick Solution

Rename vercel.json to _vercel.json then run vercel dev again and everything will be working.

For about a week I attempted to remember to keep vercel.json renamed locally– but to avoid committing that change so as not to break routing.

Lo and behold… I broke my app a few times. So then the question became: how can I prevent myself from accidentally committing the vercel.json → _vercel.json change?

Git Hooks: The Sustainable Solution

If you’re not familiar with git hooks, they let you run code whenever you run git commands. They have names like “pre-commit” (i.e., before the code is committed) or “post-receive”, and they live inside your hidden .git directory along with all the other git guts.

My idea was to:

  • Prevent _vercel.json from being committed using .gitignore
  • Prevent the deletion of vercel.json from being committed by using a pre-commit hook:

The git ignore part is easy. We just add _vercel.json to the .gitignore file.

# Don't commit unused routing file
_vercel.json

For the pre-commit hook, we can add a small script in the root of our project called prevent-delete.sh (or whatever you like) with the following contents:

# Prevent deletion of vercel.json from being committed

#D       vercel.json
REGEXP="^D[[:space:]]+vercel.json$"

if git diff --cached --name-status | grep -qE $REGEXP; then
  git reset -- vercel.json
  exit 0
fi

This script looks for a line that starts with D and then checks if it contains vercel.json. If it does, it resets the file to its original state, which in our case is deleted but not staged.

We need to make sure our script is executable by git so we run:

chmod +x prevent-delete.sh

Then we create a symlink from the git hooks directory to our script. Note– Don’t do this if your app is already setup with husky; see below.

ln -s ../../prevent-delete.sh .git/hooks/pre-commit

Now if we add all our changes with git add -A and run git status we can see that _vercel.json is being ignored, and the delete of vercel.json is staged to be committed.

Git status after add

But, when we run git commit -m "Add prevent-delete.sh" we get the following output:

Git commit output

Success! If you check git status again you’ll see that the deletion of vercel.json wasn’t committed. We can safely push this code knowing the deployment vercel creates won’t have broken routing because of a missing vercel.json file.

If you are using husky

Many apps use husky to automatically run git hooks. This is the recommended pathway for things like linting staged files.

If you’re using husky, instead of symlinking the script, just run

npx husky add .husky/pre-commit "./prevent-delete.sh"

This will add the script to the pre-commit hook.

In conclusion

That’s it! Now you can git add -A and git commit to your heart’s content without being worried about breaking your application. 🎉🎉🎉

Thanks for reading! If you have questions reach out to me on Twitter