Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

Commit 82ff378

Browse files
Merge pull request #25 from danielcondemarin/pages-subdir-support
feat: add support for nested page directories
2 parents 997c312 + a921f4b commit 82ff378

File tree

19 files changed

+501
-258
lines changed

19 files changed

+501
-258
lines changed

README.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The plugin targets [Next 8 serverless mode](https://nextjs.org/blog/next-8/#serv
2121
- [Deploying a single page](#deploying-a-single-page)
2222
- [Overriding page configuration](#overriding-page-configuration)
2323
- [Custom page routing](#custom-page-routing)
24-
- [Custom 404 error page](#custom-404-error-page)
24+
- [Custom error page](#custom-error-page)
2525
- [Examples](#examples)
2626
- [Contributing](#contributing)
2727

@@ -185,7 +185,18 @@ You can set any function property described [here](https://serverless.com/framew
185185

186186
## Custom page routing
187187

188-
The default page route is `/{pageName}`. You may want to serve your page from a different path. This is possible by setting your own http path in the `pageConfig`. For example for `pages/post.js`:
188+
The default page routes follow the same convention as next `useFileSystemPublicRoutes` documented [here](https://nextjs.org/docs/#routing).
189+
190+
E.g.
191+
192+
| page | path |
193+
| --------------------------- | ------------------- |
194+
| pages/index.js | / |
195+
| pages/post.js | /post |
196+
| pages/blog/index.js | /blog |
197+
| pages/categories/uno/dos.js | /categories/uno/dos |
198+
199+
You may want to serve your page from a different path. This is possible by setting your own http path in the `pageConfig`. For example for `pages/post.js`:
189200

190201
```js
191202
class Post extends React.Component {
@@ -220,16 +231,27 @@ custom:
220231
slug: true
221232
```
222233

223-
## Custom 404 error page
234+
## Custom error page
224235

225-
By default, Amazon API Gateway returns 403 responses when a given route doesn't exist. Instead, the plugin makes sure the [\_error page](https://nextjs.org/docs/#custom-error-handling) is rendered. That way you can customise how your 404 error page looks like.
236+
404 or 500 errors are handled both client and server side by a default component `error.js`, same as documented [here](https://github.com/zeit/next.js/#custom-error-handling).
226237

227238
Simply add `pages/_error.js`:
228239

229240
```js
230241
class Error extends React.Component {
242+
static getInitialProps({ res, err }) {
243+
const statusCode = res ? res.statusCode : err ? err.statusCode : null;
244+
return { statusCode };
245+
}
246+
231247
render() {
232-
return <p>404 page not found. (╯°□°)╯︵ ┻━┻</p>;
248+
return (
249+
<p>
250+
{this.props.statusCode
251+
? `An error ${this.props.statusCode} occurred on server (╯°□°)╯︵ ┻━┻`
252+
: "An error occurred on client"}
253+
</p>
254+
);
233255
}
234256
}
235257

__tests__/index.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe("ServerlessNextJsPlugin", () => {
6363

6464
return plugin.buildNextPages().then(() => {
6565
expect(plugin.serverless.service.package.include).toContain(
66-
`${nextConfigDir}/sls-next-build/*`
66+
`${nextConfigDir}/sls-next-build/**`
6767
);
6868
});
6969
});
@@ -79,7 +79,7 @@ describe("ServerlessNextJsPlugin", () => {
7979

8080
return plugin.buildNextPages().then(() => {
8181
expect(plugin.serverless.service.package.include).toContain(
82-
`${nextConfigDir}/sls-next-build/*`
82+
`${nextConfigDir}/sls-next-build/**`
8383
);
8484
});
8585
});

classes/NextPage.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,22 @@ class NextPage {
3737
}
3838

3939
get pageRoute() {
40+
const pathSegments = this.pagePath.split(path.sep);
41+
4042
switch (this.pageName) {
4143
case "index":
4244
return "/";
4345
case "_error":
4446
return "/{proxy+}";
4547
default:
46-
return this.pageName;
48+
// handle pages at any subdir level
49+
// e.g. build/post.js
50+
// build/categories/post.js
51+
// build/categories/fridge/index.js
52+
return pathSegments
53+
.slice(1, pathSegments.length - 1)
54+
.concat([this.pageName])
55+
.join("/");
4756
}
4857
}
4958

classes/__tests__/NextPage.test.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ const NextPage = require("../NextPage");
33
describe("NextPage", () => {
44
describe("#constructor", () => {
55
it("should set a pagePath", () => {
6-
const pagePath = "/build/serverless/pages/home.js";
6+
const pagePath = "build/home.js";
77
const page = new NextPage(pagePath);
88

99
expect(page.pagePath).toEqual(pagePath);
1010
});
1111
});
1212

1313
describe("When is the index page", () => {
14-
const pagesDir = "/build/serverless/pages";
15-
const pagePath = `${pagesDir}/index.js`;
14+
const buildDir = "build";
15+
const pagePath = `${buildDir}/index.js`;
1616
let page;
1717

1818
beforeEach(() => {
@@ -32,8 +32,8 @@ describe("NextPage", () => {
3232
});
3333

3434
describe("When is the _error page", () => {
35-
const pagesDir = "/build/serverless/pages";
36-
const pagePath = `${pagesDir}/_error.js`;
35+
const buildDir = "build";
36+
const pagePath = `${buildDir}/_error.js`;
3737
let page;
3838

3939
beforeEach(() => {
@@ -56,33 +56,56 @@ describe("NextPage", () => {
5656
});
5757
});
5858

59+
describe("When is a nested page", () => {
60+
const buildDir = "build";
61+
const pagePath = `${buildDir}/categories/fridge/fridges.js`;
62+
let page;
63+
64+
beforeEach(() => {
65+
page = new NextPage(pagePath);
66+
});
67+
68+
describe("#serverlessFunction", () => {
69+
it("should have URI path matching subdirectories", () => {
70+
const { events } = page.serverlessFunction.fridgesPage;
71+
72+
expect(events).toHaveLength(1);
73+
74+
const httpEvent = events[0].http;
75+
76+
expect(httpEvent.path).toEqual("categories/fridge/fridges");
77+
expect(httpEvent.method).toEqual("get");
78+
});
79+
});
80+
});
81+
5982
describe("When a new instance is created", () => {
60-
const pagesDir = "/build/serverless/pages";
61-
const pagePath = `${pagesDir}/admin.js`;
83+
const buildDir = "build";
84+
const pagePath = `${buildDir}/admin.js`;
6285
let page;
6386

6487
beforeEach(() => {
6588
page = new NextPage(pagePath);
6689
});
6790

6891
it("should have pageCompatPath", () => {
69-
expect(page.pageCompatPath).toEqual(`${pagesDir}/admin.compat.js`);
92+
expect(page.pageCompatPath).toEqual(`${buildDir}/admin.compat.js`);
7093
});
7194

7295
it("should return pageOriginalPath", () => {
73-
expect(page.pageOriginalPath).toEqual(`${pagesDir}/admin.original.js`);
96+
expect(page.pageOriginalPath).toEqual(`${buildDir}/admin.original.js`);
7497
});
7598

7699
it("should return pageDir", () => {
77-
expect(page.pageDir).toEqual(pagesDir);
100+
expect(page.pageDir).toEqual(buildDir);
78101
});
79102

80103
it("should return pageName", () => {
81104
expect(page.pageName).toEqual("admin");
82105
});
83106

84107
it("should return pageHandler", () => {
85-
expect(page.pageHandler).toEqual("/build/serverless/pages/admin.render");
108+
expect(page.pageHandler).toEqual("build/admin.render");
86109
});
87110

88111
it("should return pageFunctionName", () => {
@@ -97,7 +120,7 @@ describe("NextPage", () => {
97120

98121
it("should return function handler", () => {
99122
const { handler } = page.serverlessFunction.adminPage;
100-
expect(handler).toEqual(`${pagesDir}/admin.render`);
123+
expect(handler).toEqual(`${buildDir}/admin.render`);
101124
});
102125

103126
it("should return function http event", () => {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from "react";
2+
import "../../styles/home.css";
3+
4+
// categories/uno/dos
5+
function CategoriesUnoDos() {
6+
return (
7+
<div className="homePage">
8+
<div>Awesome! Nested routes ⚡</div>
9+
</div>
10+
);
11+
}
12+
13+
export default CategoriesUnoDos;

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class ServerlessNextJsPlugin {
5353
const servicePackage = this.serverless.service.package;
5454

5555
servicePackage.include = servicePackage.include || [];
56-
servicePackage.include.push(path.join(pluginBuildDir.buildDir, "*"));
56+
servicePackage.include.push(path.join(pluginBuildDir.buildDir, "**"));
5757
return build(pluginBuildDir, this.getPluginConfigValue("pageConfig")).then(
5858
nextPages => this.setNextPages(nextPages)
5959
);

integration/__tests__/local-deploy.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ describe("Local Deployment Tests (via serverless-offline)", () => {
4747
);
4848
});
4949

50+
it("should render nested fridges page", () => {
51+
expect.assertions(2);
52+
53+
return httpGet("http://localhost:3000/categories/fridge/fridges").then(
54+
({ response, statusCode }) => {
55+
expect(statusCode).toBe(200);
56+
expect(response).toContain("Fridges");
57+
}
58+
);
59+
});
60+
5061
it("should render _error page when 404", () => {
5162
expect.assertions(2);
5263

0 commit comments

Comments
 (0)