Skip to content
This repository was archived by the owner on May 10, 2021. It is now read-only.

Commit f49155d

Browse files
committed
Merge branch 'expose-function-context' into main (#119)
When a page is being SSR-ed by a Netlify Function, allow users to access the function's event and context parameters. These can be accessed as a property on the `req` object in all SSR-ed pages and in API routes: - req.netlifyFunctionParams.event - req.netlifyFunctionParams.context ```js const Page = () => <p>Hello World!</p>; export const getServerSideProps = async ({ req }) => { // Get event and context from Netlify Function const { netlifyFunctionParams: { event, context }, } = req; // Access Netlify identity const { identity, user } = context.clientContext; // Modify callbackWaitsForEmptyEventLoop behavior context.callbackWaitsForEmptyEventLoop = false; // See how much time is remaining before function timeout const timeRemaining = context.getRemainingTimeInMillis(); return { props: {}, }; }; export default Page; ``` This allows users to access/leverage Netlify identity for their Next.js page (see #20). It also allows users to modify the callbackWaitsForEmptyEventLoop behavior (see #66 (comment)). Fixes #20
2 parents 5ffce26 + 5fa6c5a commit f49155d

File tree

8 files changed

+154
-8
lines changed

8 files changed

+154
-8
lines changed

Diff for: README.md

+47
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ The plugin can be found on [npm here](https://www.npmjs.com/package/@netlify/plu
4747
- [Preview Locally](#preview-locally)
4848
- [Custom Netlify Redirects](#custom-netlify-redirects)
4949
- [Custom Netlify Functions](#custom-netlify-functions)
50+
- [Using Netlify Identity](#using-netlify-identity)
5051
- [Caveats](#caveats)
5152
- [Fallbacks for Pages with `getStaticPaths`](#fallbacks-for-pages-with-getstaticpaths)
5253
- [Credits](#credits)
@@ -199,6 +200,52 @@ Currently, there is no support for redirects set in your `netlify.toml` file.
199200
`next-on-netlify` creates one Netlify Function for each of your
200201
SSR pages and API endpoints. It is currently not possible to create custom Netlify Functions. This feature is on our list to do.
201202

203+
#### Using Netlify Identity
204+
205+
You can use [Netlify Identity](https://docs.netlify.com/visitor-access/identity/) with `next-on-netlify`. For all pages with server-side rendering (getInitialProps*, getServerSideProps, and API routes), you can access the [clientContext object](https://docs.netlify.com/functions/functions-and-identity/#access-identity-info-via-clientcontext) via the `req` parameter.
206+
207+
For example:
208+
209+
```js
210+
const Page = () => <p>Hello World!</p>;
211+
212+
export const getServerSideProps = async ({ req }) => {
213+
// Get event and context from Netlify Function
214+
const {
215+
netlifyFunctionParams: { event, context },
216+
} = req;
217+
218+
// Access Netlify identity
219+
const { identity, user } = context.clientContext;
220+
221+
return {
222+
props: {},
223+
};
224+
};
225+
226+
export default Page;
227+
```
228+
229+
To access Netlify Identity from pages without server-side rendering, you can create a [Next API route](https://nextjs.org/docs/api-routes/introduction) that performs identity-related logic:
230+
231+
```js
232+
export default async function getUser(req, res) {
233+
// Get event and context from Netlify Function
234+
const {
235+
netlifyFunctionParams: { event, context },
236+
} = req;
237+
238+
// Access Netlify identity
239+
const { user } = context.clientContext;
240+
241+
// Respond with user object
242+
res.json({ user });
243+
}
244+
```
245+
246+
\* Note that pages using getInitialProps are only server-side rendered on initial page load and not when the user navigates client-side between pages.
247+
248+
202249
## Caveats
203250

204251
### Fallbacks for Pages with `getStaticPaths`

Diff for: cypress/fixtures/pages/api/context.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default async function context(req, res) {
2+
res.json({ req, res });
3+
}

Diff for: cypress/fixtures/pages/getServerSideProps/context.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const Context = ({ context }) => <pre>{JSON.stringify(context, 2, " ")}</pre>;
2+
3+
export const getServerSideProps = async (context) => {
4+
return {
5+
props: {
6+
context,
7+
},
8+
};
9+
};
10+
11+
export default Context;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const WaitForEmptyEventLoop = () => <p>Successfully rendered page!</p>;
2+
3+
export const getServerSideProps = async ({ params, req }) => {
4+
// Set up long-running process
5+
const timeout = setTimeout(() => {}, 100000);
6+
7+
// Set behavior of whether to wait for empty event loop
8+
const wait = String(params.wait).toLowerCase() === "true";
9+
const { context: functionContext } = req.netlifyFunctionParams;
10+
functionContext.callbackWaitsForEmptyEventLoop = wait;
11+
12+
return {
13+
props: {},
14+
};
15+
};
16+
17+
export default WaitForEmptyEventLoop;

Diff for: cypress/integration/default_spec.js

+64
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,49 @@ describe("getInitialProps", () => {
136136
});
137137

138138
describe("getServerSideProps", () => {
139+
it("exposes function context on the req object", () => {
140+
cy.visit("/getServerSideProps/context");
141+
142+
cy.get("pre")
143+
.first()
144+
.then((json) => {
145+
const {
146+
req: {
147+
netlifyFunctionParams: { event, context },
148+
},
149+
} = JSON.parse(json.html());
150+
151+
expect(event).to.have.property("path", "/getServerSideProps/context");
152+
expect(event).to.have.property("httpMethod", "GET");
153+
expect(event).to.have.property("headers");
154+
expect(event).to.have.property("multiValueHeaders");
155+
expect(event).to.have.property("isBase64Encoded");
156+
expect(context.done).to.be.undefined;
157+
expect(context.getRemainingTimeInMillis).to.be.undefined;
158+
expect(context).to.have.property("awsRequestId");
159+
expect(context).to.have.property("callbackWaitsForEmptyEventLoop");
160+
expect(context).to.have.property("clientContext");
161+
});
162+
});
163+
164+
it("can modify the callbackWaitsForEmptyEventLoop behavior", () => {
165+
// netlify dev never waits on empty event loop
166+
if (Cypress.env("DEPLOY") !== "local") {
167+
cy.request({
168+
url: "/getServerSideProps/wait-on-empty-event-loop/true",
169+
failOnStatusCode: false,
170+
// Functions time out after 10s, so we need to wait a bit
171+
timeout: 15000,
172+
}).then((response) => {
173+
expect(response.status).to.eq(502);
174+
expect(response.body).to.contain("Task timed out");
175+
});
176+
}
177+
178+
cy.visit("/getServerSideProps/wait-on-empty-event-loop/false");
179+
cy.get("p").should("contain", "Successfully rendered page!");
180+
});
181+
139182
context("with static route", () => {
140183
it("loads TV shows", () => {
141184
cy.visit("/getServerSideProps/static");
@@ -534,6 +577,27 @@ describe("API endpoint", () => {
534577
cy.get("h1").should("contain", "Show #999");
535578
cy.get("p").should("contain", "Flash Gordon");
536579
});
580+
581+
it("exposes function context on the req object", () => {
582+
cy.request("/api/context").then((response) => {
583+
const {
584+
req: {
585+
netlifyFunctionParams: { event, context },
586+
},
587+
} = response.body;
588+
589+
expect(event).to.have.property("path", "/api/context");
590+
expect(event).to.have.property("httpMethod", "GET");
591+
expect(event).to.have.property("headers");
592+
expect(event).to.have.property("multiValueHeaders");
593+
expect(event).to.have.property("isBase64Encoded");
594+
expect(context.done).to.be.undefined;
595+
expect(context.getRemainingTimeInMillis).to.be.undefined;
596+
expect(context).to.have.property("awsRequestId");
597+
expect(context).to.have.property("callbackWaitsForEmptyEventLoop");
598+
expect(context).to.have.property("clientContext");
599+
});
600+
});
537601
});
538602

539603
describe("Preview Mode", () => {

Diff for: lib/templates/createRequestObject.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const http = require("http");
66
// Based on API Gateway Lambda Compat
77
// Source: https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/compat-layers/apigw-lambda-compat/lib/compatLayer.js
88

9-
const createRequestObject = ({ event }) => {
9+
const createRequestObject = ({ event, context }) => {
1010
const {
1111
requestContext = {},
1212
path = "",
@@ -52,6 +52,14 @@ const createRequestObject = ({ event }) => {
5252
req.rawHeaders = [];
5353
req.headers = {};
5454

55+
// Expose Netlify Function event and callback on request object.
56+
// This makes it possible to access the clientContext, for example.
57+
// See: https://github.com/netlify/next-on-netlify/issues/20
58+
// It also allows users to change the behavior of waiting for empty event
59+
// loop.
60+
// See: https://github.com/netlify/next-on-netlify/issues/66#issuecomment-719988804
61+
req.netlifyFunctionParams = { event, context };
62+
5563
for (const key of Object.keys(multiValueHeaders)) {
5664
for (const value of multiValueHeaders[key]) {
5765
req.rawHeaders.push(key);

Diff for: lib/templates/netlifyFunction.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@ exports.handler = async (event, context, callback) => {
1616
console.log("[request]", path);
1717

1818
// Render the Next.js page
19-
const response = await renderNextPage({
20-
...event,
21-
// Required. Otherwise, reqResMapper will complain
22-
requestContext: {},
23-
});
19+
const response = await renderNextPage({ event, context });
2420

2521
// Convert header values to string. Netlify does not support integers as
2622
// header values. See: https://github.com/netlify/cli/issues/451

Diff for: lib/templates/renderNextPage.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ const createRequestObject = require("./createRequestObject");
44
const createResponseObject = require("./createResponseObject");
55

66
// Render the Next.js page
7-
const renderNextPage = (event) => {
7+
const renderNextPage = ({ event, context }) => {
88
// The Next.js page is rendered inside a promise that is resolved when the
99
// Next.js page ends the response via `res.end()`
1010
const promise = new Promise((resolve) => {
1111
// Create a Next.js-compatible request and response object
1212
// These mock the ClientRequest and ServerResponse classes from node http
1313
// See: https://nodejs.org/api/http.html
14-
const req = createRequestObject({ event });
14+
const req = createRequestObject({ event, context });
1515
const res = createResponseObject({
1616
onResEnd: (response) => resolve(response),
1717
});

0 commit comments

Comments
 (0)