diff --git a/cypress/integration/middleware/enhanced.spec.ts b/cypress/integration/middleware/enhanced.spec.ts index e7541bca91..9fc8bfe06f 100644 --- a/cypress/integration/middleware/enhanced.spec.ts +++ b/cypress/integration/middleware/enhanced.spec.ts @@ -25,4 +25,15 @@ describe('Enhanced middleware', () => { .that.includes('This was static but has been transformed in') }) }) + + it('adds geo data', () => { + cy.request('/api/geo').then((response) => { + expect(response.body).to.have.nested.property('headers.x-geo-country') + expect(response.body).to.have.nested.property('headers.x-geo-region') + expect(response.body).to.have.nested.property('headers.x-geo-city') + expect(response.body).to.have.nested.property('headers.x-geo-longitude') + expect(response.body).to.have.nested.property('headers.x-geo-latitude') + expect(response.body).to.have.nested.property('headers.x-geo-timezone') + }) + }) }) diff --git a/demos/middleware/middleware.ts b/demos/middleware/middleware.ts index 8af64d3157..1816dd39bc 100644 --- a/demos/middleware/middleware.ts +++ b/demos/middleware/middleware.ts @@ -1,7 +1,5 @@ import { NextResponse } from 'next/server' -import type { NextRequest } from 'next/server' - -import { MiddlewareRequest } from '@netlify/next' +import { MiddlewareRequest, NextRequest } from '@netlify/next' export async function middleware(req: NextRequest) { let response @@ -29,6 +27,17 @@ export async function middleware(req: NextRequest) { return request.next() } + if (pathname.startsWith('/api/geo')) { + req.headers.set('x-geo-country', req.geo.country) + req.headers.set('x-geo-region', req.geo.region) + req.headers.set('x-geo-city', req.geo.city) + req.headers.set('x-geo-longitude', req.geo.longitude) + req.headers.set('x-geo-latitude', req.geo.latitude) + req.headers.set('x-geo-timezone', req.geo.timezone) + + return request.next() + } + if (pathname.startsWith('/headers')) { // Add a header to the rewritten request req.headers.set('x-hello', 'world') diff --git a/demos/middleware/pages/api/geo.js b/demos/middleware/pages/api/geo.js new file mode 100644 index 0000000000..84acde12b3 --- /dev/null +++ b/demos/middleware/pages/api/geo.js @@ -0,0 +1,3 @@ +export default function handler(req, res) { + res.status(200).json({ name: 'geo-test', headers: req.headers }) +} diff --git a/packages/next/src/middleware/request.ts b/packages/next/src/middleware/request.ts index f04554ad7d..737ea90b37 100644 --- a/packages/next/src/middleware/request.ts +++ b/packages/next/src/middleware/request.ts @@ -1,9 +1,15 @@ import type { NextURL } from 'next/dist/server/web/next-url' import { NextResponse } from 'next/server' -import type { NextRequest } from 'next/server' +import type { NextRequest as InternalNextRequest } from 'next/server' import { MiddlewareResponse } from './response' +export type NextRequest = InternalNextRequest & { + get geo(): { + timezone?: string + } +} + export interface NextOptions { /** * Include conditional request headers in the request to the origin. diff --git a/packages/next/test/request.spec.ts b/packages/next/test/request.spec.ts index 2fb7a64494..a421de9544 100644 --- a/packages/next/test/request.spec.ts +++ b/packages/next/test/request.spec.ts @@ -25,6 +25,9 @@ describe('MiddlewareRequest', () => { code: chance.province(), }, city: chance.city(), + latitude: chance.latitude(), + longitude: chance.longitude(), + timezone: chance.timezone(), }, ip, } @@ -33,6 +36,9 @@ describe('MiddlewareRequest', () => { country: context.geo.country?.code, region: context.geo.subdivision?.code, city: context.geo.city, + latitude: context.geo.latitude?.toString(), + longitude: context.geo.longitude?.toString(), + timezone: context.geo.timezone, } const req = new URL(url) diff --git a/packages/runtime/src/templates/edge/next-dev.js b/packages/runtime/src/templates/edge/next-dev.js index 8575fcb9e4..094725f6f6 100644 --- a/packages/runtime/src/templates/edge/next-dev.js +++ b/packages/runtime/src/templates/edge/next-dev.js @@ -43,11 +43,14 @@ const handler = async (req, context) => { return } - // This is the format expected by Next.js + // This is the format expected by Next.js along with the timezone which we support. const geo = { country: context.geo.country?.code, region: context.geo.subdivision?.code, city: context.geo.city, + latitude: context.geo.latitude?.toString(), + longitude: context.geo.longitude?.toString(), + timezone: context.geo.timezone, } // A default request id is fine locally diff --git a/packages/runtime/src/templates/edge/runtime.ts b/packages/runtime/src/templates/edge/runtime.ts index a636320f21..59d7a4a9cd 100644 --- a/packages/runtime/src/templates/edge/runtime.ts +++ b/packages/runtime/src/templates/edge/runtime.ts @@ -20,6 +20,7 @@ export interface RequestData { region?: string latitude?: string longitude?: string + timezone?: string } headers: Record ip?: string @@ -63,10 +64,13 @@ const handler = async (req: Request, context: Context) => { return } - const geo = { + const geo: RequestData['geo'] = { country: context.geo.country?.code, region: context.geo.subdivision?.code, city: context.geo.city, + latitude: context.geo.latitude?.toString(), + longitude: context.geo.longitude?.toString(), + timezone: context.geo.timezone, } const requestId = req.headers.get('x-nf-request-id')