Skip to content

Commit 935544a

Browse files
committed
Add support for NextRequest geolocation
1 parent 0a4b952 commit 935544a

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

.changeset/famous-pumpkins-cover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"open-next": minor
3+
---
4+
5+
Add support for NextRequest geolocation

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,57 @@ function handler(event) {
247247

248248
The server function would then sets the `host` header of the request to the value of the `x-forwarded-host` header when sending the request to the `NextServer`.
249249

250+
#### WORKAROUND: Set `NextRequest` geolocation data
251+
252+
When your application is hosted on Vercel, you can access a user's geolocation inside your middleware through the `NextRequest` object.
253+
254+
```ts
255+
export function middleware(request: NextRequest) {
256+
request.geo.country;
257+
request.geo.city;
258+
}
259+
```
260+
261+
When your application is hosted on AWS, you can [obtain the geolocation data from CloudFront request headers](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html#cloudfront-headers-viewer-location). However, there is no way to set this data on the `NextRequest` object passed to the middleware function.
262+
263+
To work around the issue, the `NextRequest` constructor is modified to initialize geolocation data from CloudFront headers, instead of using the default empty object.
264+
265+
```diff
266+
- geo: init.geo || {}
267+
+ geo: init.geo || {
268+
+ country: this.headers("cloudfront-viewer-country"),
269+
+ countryName: this.headers("cloudfront-viewer-country-name"),
270+
+ region: this.headers("cloudfront-viewer-country-region"),
271+
+ regionName: this.headers("cloudfront-viewer-country-region-name"),
272+
+ city: this.headers("cloudfront-viewer-city"),
273+
+ postalCode: this.headers("cloudfront-viewer-postal-code"),
274+
+ timeZone: this.headers("cloudfront-viewer-time-zone"),
275+
+ latitude: this.headers("cloudfront-viewer-latitude"),
276+
+ longitude: this.headers("cloudfront-viewer-longitude"),
277+
+ metroCode: this.headers("cloudfront-viewer-metro-code"),
278+
+ }
279+
```
280+
281+
CloudFront provides more detailed geolocation information, such as postal code and timezone. Here is a complete list of `geo` properties available in your middleware:
282+
283+
```ts
284+
export function middleware(request: NextRequest) {
285+
// Supported by Next.js
286+
request.geo.country;
287+
request.geo.region;
288+
request.geo.city;
289+
request.geo.latitude;
290+
request.geo.longitude;
291+
292+
// Also supported by OpenNext
293+
request.geo.countryName;
294+
request.geo.regionName;
295+
request.geo.postalCode;
296+
request.geo.timeZone;
297+
request.geo.metroCode;
298+
}
299+
```
300+
250301
#### WORKAROUND: `NextServer` does not set cache response headers for HTML pages
251302

252303
As mentioned in the [Server function](#server-lambda-function) section, the server function uses the `NextServer` class from Next.js' build output to handle requests. However, `NextServer` does not seem to set the correct `Cache Control` headers.

packages/open-next/src/build.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,39 @@ function printVersion() {
114114
console.info(`Using v${pkg.version}`);
115115
}
116116

117+
function injectMiddlewareGeolocation(outputPath: string, packagePath: string) {
118+
const basePath = path.join(outputPath, packagePath, ".next", "server");
119+
const rootMiddlewarePath = path.join(basePath, "middleware.js");
120+
const srcMiddlewarePath = path.join(basePath, "src", "middleware.js");
121+
if (fs.existsSync(rootMiddlewarePath)) {
122+
inject(rootMiddlewarePath);
123+
} else if (fs.existsSync(srcMiddlewarePath)) {
124+
inject(srcMiddlewarePath);
125+
}
126+
127+
function inject(middlewarePath: string) {
128+
const content = fs.readFileSync(middlewarePath, "utf-8");
129+
fs.writeFileSync(
130+
middlewarePath,
131+
content.replace(
132+
"geo: init.geo || {}",
133+
`geo: init.geo || {
134+
country: this.headers.get("cloudfront-viewer-country"),
135+
countryName: this.headers.get("cloudfront-viewer-country-name"),
136+
region: this.headers.get("cloudfront-viewer-country-region"),
137+
regionName: this.headers.get("cloudfront-viewer-country-region-name"),
138+
city: this.headers.get("cloudfront-viewer-city"),
139+
postalCode: this.headers.get("cloudfront-viewer-postal-code"),
140+
timeZone: this.headers.get("cloudfront-viewer-time-zone"),
141+
latitude: this.headers.get("cloudfront-viewer-latitude"),
142+
longitude: this.headers.get("cloudfront-viewer-longitude"),
143+
metroCode: this.headers.get("cloudfront-viewer-metro-code"),
144+
}`
145+
)
146+
);
147+
}
148+
}
149+
117150
function initOutputDir() {
118151
fs.rmSync(outputDir, { recursive: true, force: true });
119152
fs.mkdirSync(tempDir, { recursive: true });
@@ -207,6 +240,9 @@ function createServerBundle(monorepoRoot: string) {
207240
path.join(outputOpenNextPath, "public-files.json"),
208241
JSON.stringify(listPublicFiles())
209242
);
243+
244+
// WORKAROUND: Set `NextRequest` geolocation data — https://github.com/serverless-stack/open-next#workaround-set-nextrequest-geolocation-data
245+
injectMiddlewareGeolocation(outputPath, packagePath);
210246
}
211247

212248
async function minifyServerBundle() {

0 commit comments

Comments
 (0)