TLDR: A functions.excludeFiles glob in vercel.json can silently prevent your Vercel Python function from being registered at all — no build error, just a mysterious Next.js 404 at runtime. Drop it if you hit this.

the setup

I was building a 7-step engagement-letter pipeline for a law practice management system — the kind of thing that renders a .docx contract using docxtpl (a Python library for Jinja2-templating Word docs), then ships it off for signatures via DocuSeal.

Before I wired up the real renderer, I did the sensible thing: set a smoke-test gate.

C2 was just /api/ping — a dead-simple Python endpoint that returns {ok, ts}. Nothing else. Before I could push the actual renderer in C3, /api/ping had to return 200 from the live Vercel deployment.

It passed. ✅

I was feeling good.

the wall

C2 also added an excludeFiles glob to vercel.json — meant to keep the Python function bundle lean once docxtpl joined the party in C3:

"functions": {
  "api/**/*.py": {
    "excludeFiles": "{tests/**,__tests__/**,fixtures/**,scripts/**,node_modules/**,.next/**,.vercel/**}"
  }
}

Totally reasonable-looking config. Exclude the junk, keep the bundle small. C2 built and deployed fine, ping worked, moving on.

C3 added the actual renderer — api/render-engagement-letter.py plus a 147KB blueprint .docx in doc_templates/. Confirmed working locally. Pushed.

GitHub deployment status: failed.

But here's the confusing part — /api/ping still worked. The renderer returned a straight Next.js 404. Not a Python 500. Not a library error. A routing-level 404 as if the route didn't exist.

I stared at it for a beat. That diagnostic is everything, by the way — a Next.js 404 on a Python route doesn't mean your code is wrong. It means Vercel never registered the function.

the fix that worked

Dropped the functions block entirely. Reverted vercel.json to the simple cron-only form. Pushed.

C4–C7 shipped clean. The whole pipeline — render, Drive upload, DocuSeal send, completion webhook — worked without touching another line of Python.

I'm fairly sure the curly-brace syntax in the glob was the problem (Vercel likely expects a single glob pattern, not {a,b,c} alternation), or it was matching something the bundler needed to register the function. I never proved the exact mechanism — just that dropping it fixed it.

the lesson that stings

My smoke-test gate passed and gave me false confidence.

/api/ping had no assets. It was bare Python. The excludeFiles glob only became a problem when a real function with a real asset (doc_templates/) showed up in C3.

The gate tested exactly what it tested, nothing more.

So: if a Vercel Python route is returning a Next.js 404 — suspect the function config before you suspect the route code. And keep excludeFiles globs simple (or just skip them) until you can verify they're not eating something the runtime needs.

Premature bundle optimization is still premature optimization, just wearing a deployment hat.