Your rewrites are not working as expected because they only control routing. Vercel has already inspected your dist directory and decided that functions/index.js is a static asset before the rewrites are even considered.
Solution: Use the Vercel Build Output API Structure
The most robust and recommended solution is to make your build script create the directory structure that Vercel expects. This gives you full control over the deployment.
1. Overview of the Plan
We will modify your build process to create a .vercel/output directory with the following structure:
.vercel/output/
├── config.json # Defines routes and function configurations
├── static/ # Your Vite static assets will go here
│ └── assets/
│ └── ...
└── functions/ # Your serverless functions go here
└── index.func/ # A special directory for each function
├── .vc-config.json # Config for this specific function
└── index.js # Your compiled Hono code
2. Step-by-Step Implementation
Step 1: Update your vercel.json
First, simplify your vercel.json. The routing and function definitions will now live inside the build output itself.
JSON
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"version": 2,
"installCommand": "pnpm install",
"buildCommand": "pnpm turbo run build",
"outputDirectory": ".vercel/output"
}
outputDirectory: We change this to .vercel/output, which is the standard for this method. All build artifacts must now go into this directory.
Step 2: Modify Your Build Scripts
Your pnpm turbo run build command now needs to orchestrate a few things: compiling the function, building the static assets, and creating the necessary configuration files.
A good way to do this is with a small shell script that runs as your main build command. Let's call it build.sh.
Update your root package.json:
JSON
// package.json
{
"scripts": {
"build": "sh ./build.sh"
}
}
Now, create the build.sh script in your project root:
Bash
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# 1. Clean up previous build output
rm -rf .vercel/output
# 2. Build the Vite static assets
# We tell Vite to output to the `static` folder within our new structure.
pnpm run build:vite
# 3. Build the Hono serverless function
# We use tsup to compile the function into the `functions` directory.
pnpm run build:function
# 4. Create the necessary Vercel config files
# This is the magic that ties everything together.
mkdir -p .vercel/output
cp vercel.config.json .vercel/output/config.json
Step 3: Configure Individual Package Builds
Now, let's configure the individual build commands (build:vite and build:function) and create the required config files.
A. Configure Vite Build (build:vite)
In your Vite app's package.json, your build script should output to the correct directory.
Update your vite.config.ts:
TypeScript
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
build: {
// This is the key change!
outDir: '../../.vercel/output/static',
assetsDir: 'assets' // This keeps the /assets/ path
},
});
B. Configure Function Build (build:function)
This is the most important part. We need to compile your TypeScript function into a specific .func directory and include a small configuration file.
Update your function's tsup.config.ts:
TypeScript
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
splitting: false,
sourcemap: true,
clean: true,
// This is the key change!
outDir: '.vercel/output/functions/index.func',
format: ['esm'], // Vercel functions should be ES Modules
});
Next, inside your function's source directory (e.g., packages/api/), create a .vc-config.json file. This tells Vercel how to run the code.
JSON
// packages/api/.vc-config.json
{
"runtime": "edge",
"entrypoint": "index.js"
}
Your function's build process needs to copy this file into the output directory. You can add this to your function's package.json script:
JSON
// packages/api/package.json
{
"scripts": {
"build": "tsup && cp .vc-config.json ../../.vercel/output/functions/index.func/"
}
}
C. Create the Main config.json
In your project root, create a file named vercel.config.json (we copy this during the build script). This file replaces your old rewrites.
JSON
// vercel.config.json
{
"version": 3,
"routes": [
{
"source": "/assets/(.*)",
"headers": {
"cache-control": "public, max-age=31536000, immutable"
},
"continue": true
},
{
"handle": "filesystem"
},
{
"src": "/(.*)",
"dest": "/index"
}
]
}
handle: "filesystem": This tells Vercel to serve any static files that match the request path (like your assets in /assets/*).
src: "/(.*)", dest: "/index": This is the catch-all route. It says "if no static file was found, send the request to the serverless function named index". This index corresponds to the index.func directory we created.