Build a GitHub Acceleration Proxy with Vercel for Free
The original motivation for this proxy was pretty simple: I needed a small tool to speed up git clone, but I didn’t want to spend money maintaining a server. Vercel’s free tier is generous on both performance and bandwidth, making it the perfect choice for freeloading.
My requirements:
- Forward all requests to
https://github.com. - Handle large
git cloneandpushoperations without restrictions. - Return a simple usage text when visiting
/. - Set up
/robots.txtto block search engine indexing.
Reference from @lideming’s https://md.yuuza.net/s/U52dzJZWd
Why TypeScript + Edge?
I didn’t start with TypeScript. As someone comfortable with backend, Go was my first instinct, since Go’s ReverseProxy is straightforward and efficient.
I quickly implemented the first version in Go and deployed it to Vercel. Small-scale testing looked fine.
But as soon as I tried git clone on a slightly larger repo, Vercel’s cloud logs showed 413 Payload Too Large.
Go’s failure was a Serverless architecture limitation
The problem wasn’t the Go code itself, but the environment it ran in.
Vercel’s standard Serverless Function (which is where Go runs) is built on AWS Lambda and similar technologies. To handle a request, it buffers the entire request body before passing it to your code. When you’re transferring a Git data pack of several MB or more, the data hits Vercel’s 4.5MB limit1 before it even reaches your Go handler, returning a 413 error.
This made me realize that to handle large file transfers, I needed to bypass the buffering limit and use streaming instead.
Edge Runtime: Solving 413
Vercel’s Edge Runtime2 is based on Web Standards (like Fetch API and Web Streams), designed for I/O-intensive tasks. It natively supports streaming request and response bodies. Data flows through Edge nodes like water, never accumulating in memory, so the payload size limit simply doesn’t apply.
Since we’re going Edge, TypeScript/JavaScript is the most natural, lightweight choice with the fastest cold start.
Core Edge Runtime Implementation
The core logic lives in api/index.ts. We need to tell Vercel explicitly that this file should run on the Edge Runtime.
// Core: Tell Vercel this must run on Edge Runtime
export const config = {
runtime: 'edge',
};
export default async function handler(req: Request) {
const url = new URL(req.url);
const path = url.pathname;
// 1. Highest priority route: robots.txt
if (path === '/robots.txt') {
return new Response('User-agent: *\nDisallow: /', { status: 200 });
}
// 2. Home route: return usage instructions
if (path === '/') {
const usageText = `USAGE
----------------
Clone a repo:
git clone https://github.therainisme.com/<user>/<repo>.git
...
`;
return new Response(usageText, {
headers: { 'content-type': 'text/plain; charset=utf-8' },
});
}
// 3. Core proxy forwarding logic
const targetUrl = new URL(req.url);
// Fix the target address to ensure forwarding to github.com
targetUrl.protocol = 'https:';
targetUrl.hostname = 'github.com';
targetUrl.port = '';
const newReq = new Request(targetUrl.toString(), {
method: req.method,
headers: req.headers,
// Key: req.body is a ReadableStream, pass it directly for streaming
body: req.body,
redirect: 'manual',
});
// Must set the correct Host header, otherwise GitHub will reject the request
newReq.headers.set('Host', 'github.com');
// Remove X-Forwarded-For etc. to keep the request clean
newReq.headers.delete('X-Forwarded-For');
// Forward the request and return the response
const resp = await fetch(newReq);
// Return the Response, ensuring streaming data is passed through correctly
return new Response(resp.body, {
status: resp.status,
statusText: resp.statusText,
headers: resp.headers,
});
}
Only proxy requests ending in .git and .png
Important! If we forward all requests, the domain registrar might consider this a phishing site and ban the domain. To avoid this risk, we need path filtering to only allow requests ending in .git or .png:
export default async function handler(req: Request) {
const url = new URL(req.url);
const path = url.pathname;
// robots.txt and home route handling (same as above, omitted)...
// Important: only proxy requests ending in .git or .png
if (!path.endsWith('.git') && !path.endsWith('.png')) {
return new Response('Forbidden: Only .git and .png requests are allowed', {
status: 403,
headers: { 'content-type': 'text/plain; charset=utf-8' },
});
}
// Rest of the proxy forwarding logic...
}
Two small deployment hurdles
The code was done, but deploying to Vercel came with a couple issues worth noting:
Missing
publicdirectory: Since this is a pure API service, there are no frontend build artifacts in the project root. Vercel might throwError: No Output Directory named "public" found.
Fix: Create an emptypublicfolder in the project root with aplaceholder.htmlinside.Root route hijacked by static files: To fix the above, I placed files in
public, but Vercel’s default routing gives static files higher priority than API routes. So visiting/servedpublic/index.htmlinstead of the usage text from my TypeScript handler.
Fix: Don’t name the home page fileindex.html. Renamingpublic/index.htmltopublic/placeholder.htmlmeans/has no static file to intercept, and traffic flows toapi/index.tsas intended.
Conclusion
Works great! Easy to use!
But it seems to only work well in Shanghai. Based on feedback from friends, Beijing, Shenzhen, and Xi’an still experience lag. Also note Vercel’s Edge Runtime has a 300-second time limit3.