I have a Next.js application deployed on Vercel and a WordPress blog running on an external server (nginx/Apache). I’d like all requests to /blog/* on my Next.js app to be proxied or redirected to the external server.
However, Vercel automatically strips trailing slashes from every URL. My WordPress setup enforces trailing slashes, so it issues a 301 Moved Permanently to the URL with the slash. Vercel then removes the slash again, triggering another 301 from WordPress—and so on, ad infinitum.
What I’ve Tried:
- vercel.json rewrite/redirect rules
- next.config.js with trailingSlash: true / false
- middleware.ts (Next.js Edge Middleware) to normalize slashes and proxy headers
None of these approaches have stopped the slash‑removal ↔ slash‑addition ping‑pong.
// vercel.json
{
"redirects": [
{
"source": "/blog/", // Match requests specifically ending with /blog/
"destination": "https://my-wp-blog.com/blog/", // Ensure destination has the needed slash
"permanent": true // Or false (use 301/302 if temporary, 307/308 if permanent - true often defaults to 308)
},
]
}
// next.config.js
module.exports = {
trailingSlash: true,
}
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone()
// Force trailing slash for all blog paths
if (url.pathname.startsWith('/blog') && !url.pathname.endsWith('/')) {
const redirectUrl = new URL(request.url)
redirectUrl.pathname = `${url.pathname}/`
return NextResponse.redirect(redirectUrl, 308) // 308 Permanent Redirect preserves method
}
// Continue with proxying for requests that already have trailing slashes
const requestHeaders = new Headers(request.headers)
requestHeaders.set('host', 'my-wp-blog.com')
const clientIP = request.ip || request.headers.get('x-forwarded-for') || ''
requestHeaders.set('x-real-ip', clientIP)
requestHeaders.set('x-forwarded-for', clientIP)
requestHeaders.set('x-forwarded-proto', request.nextUrl.protocol.replace(':', ''))
requestHeaders.set('x-forwarded-host', 'my-wp-blog.com')
// Also explicitly ensure the proxied URL has a trailing slash
const proxyUrl = new URL(request.url)
proxyUrl.protocol = 'https:'
proxyUrl.hostname = 'my-blog-server-ip'
// Make sure the pathname still has the trailing slash when proxying
if (!proxyUrl.pathname.endsWith('/')) {
proxyUrl.pathname = `${proxyUrl.pathname}/`
}
return NextResponse.rewrite(proxyUrl, {
request: {
headers: requestHeaders,
},
})
}
export const config = {
matcher: '/blog/:path*',
}
Example of the Loop (via curl -vIL):
> HEAD /blog/my-post/ HTTP/2
< HTTP/2 308 # Vercel removes the slash
< location: /blog/my-post
> HEAD /blog/my-post HTTP/2
< HTTP/2 301 # WordPress adds the slash
< location: https://example.com/blog/my-post/
…repeat indefinitely…
So my questions are:
How can I disable Vercel/Next.js’s automatic trailing‑slash redirects entirely so that my middleware can fully control URL normalization?
Is it possible to use both vercel.json and Edge Middleware together to achieve this, or must I choose one approach?
Are there any additional headers or settings I need to tweak on Vercel or my external server to break out of this redirect loop?
Any guidance on a reliable pattern for proxying /blog to an external WordPress host—without triggering infinite redirects—would be greatly appreciated