├── src
└── functions
│ ├── pages
│ └── .gitkeep
│ ├── index.js
│ └── post.js
├── netlify.toml
├── .gitignore
├── next.config.js
├── public
└── _redirects
├── package.json
└── pages
├── index.js
└── post.js
/src/functions/pages/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | functions="functions"
3 | publish="public/"
4 | command="yarn build"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /functions/
3 | public/_next/
4 | src/functions/pages
5 | .next
6 | build/
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | distDir: 'public/_next',
3 | target: 'serverless'
4 | };
5 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | / /.netlify/functions/index 200
2 | /posts/:post_id /.netlify/functions/post/:post_id 200
--------------------------------------------------------------------------------
/src/functions/index.js:
--------------------------------------------------------------------------------
1 | const compat = require("next-aws-lambda");
2 | const page = require("./pages/index");
3 |
4 | exports.handler = (event, context, callback) => {
5 | console.log("[render] ", event.path)
6 | event.requestContext = {}
7 | compat(page)(event, context, callback)
8 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-on-netlify",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "amp-toolbox-optimizer": "^0.5.3",
8 | "axios": "^0.18.0",
9 | "encoding": "^0.1.12",
10 | "netlify-lambda": "^1.4.11",
11 | "next": "^8.1.0",
12 | "next-aws-lambda": "^2.0.0-alpha.0",
13 | "react": "^16.8.6",
14 | "react-dom": "^16.8.6"
15 | },
16 | "scripts": {
17 | "dev": "next",
18 | "build:next": "next build",
19 | "build": "yarn build:next && cp -a public/_next/serverless/pages src/functions/ && netlify-lambda build src/functions",
20 | "start": "next start"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import Link from 'next/link'
3 | import Head from 'next/head'
4 | import { withRouter } from 'next/router'
5 |
6 | const Home = ({ posts }) => {
7 | return
8 |
9 |
Next on Netlify
10 |
11 | {
12 | posts.map(post => (
13 |
20 | ))
21 | }
22 |
;
23 | }
24 |
25 | Home.getInitialProps = async props => {
26 | if (props.res) {
27 | console.log('setHeader')
28 | props.res.setHeader('Cache-Control', 'public, s-maxage=30, stale-while-revalidate')
29 | }
30 |
31 | console.log(props.asPath, props.query)
32 |
33 | const { data } = await axios.get('https://netlify-json-api.netlify.com/posts')
34 |
35 | return { posts: data }
36 | }
37 |
38 | export default withRouter(Home);
39 |
--------------------------------------------------------------------------------
/pages/post.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import Link from 'next/link'
3 | import Head from 'next/head'
4 |
5 | const Post = ({ post }) => {
6 | const title = `${post.title} | Next on Netlify`
7 | const description = "Next.jsをNetlify上で動かすデモです。"
8 | return
9 |
10 |
{title}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
HOME
22 |
23 |
{post.title}
24 |
{post.description}
25 |
;
26 | }
27 |
28 | Post.getInitialProps = async props => {
29 | if (props.res) {
30 | console.log('setHeader')
31 | props.res.setHeader('Cache-Control', 'public, s-maxage=30, stale-while-revalidate')
32 | }
33 |
34 | console.log('[renderApp]', props.asPath, props.query, props.req && props.req.query.id)
35 | const id = props.req ? props.req.query.id : props.query.id
36 |
37 | const { data } = await axios.get(`https://netlify-json-api.netlify.com/posts/${id}`)
38 |
39 | return { post: data }
40 | }
41 |
42 | export default Post;
43 |
--------------------------------------------------------------------------------
/src/functions/post.js:
--------------------------------------------------------------------------------
1 |
2 | const Stream = require("stream");
3 | const queryString = require("querystring");
4 |
5 | const reqResMapper = (event, callback) => {
6 | const base64Support = process.env.BINARY_SUPPORT === "yes";
7 | const response = {
8 | body: Buffer.from(""),
9 | isBase64Encoded: base64Support,
10 | statusCode: 200,
11 | multiValueHeaders: {}
12 | };
13 |
14 | const req = new Stream.Readable();
15 | req.url = (event.requestContext.path || event.path || "").replace(
16 | new RegExp("^/" + event.requestContext.stage),
17 | ""
18 | );
19 |
20 | let qs = "";
21 |
22 | if (event.multiValueQueryStringParameters) {
23 | qs += queryString.stringify(event.multiValueQueryStringParameters);
24 | }
25 |
26 | if (event.pathParameters) {
27 | const pathParametersQs = queryString.stringify(event.pathParameters);
28 |
29 | if (qs.length > 0) {
30 | qs += `&${pathParametersQs}`;
31 | } else {
32 | qs += pathParametersQs;
33 | }
34 | }
35 |
36 | const hasQueryString = qs.length > 0;
37 |
38 | if (hasQueryString) {
39 | req.url += `?${qs}`;
40 | }
41 |
42 | req.method = event.httpMethod;
43 | req.rawHeaders = [];
44 | req.headers = {};
45 |
46 | const headers = event.multiValueHeaders || {};
47 |
48 | for (const key of Object.keys(headers)) {
49 | for (const value of headers[key]) {
50 | req.rawHeaders.push(key);
51 | req.rawHeaders.push(value);
52 | }
53 | req.headers[key.toLowerCase()] = headers[key].toString();
54 | }
55 |
56 | req.getHeader = name => {
57 | return req.headers[name.toLowerCase()];
58 | };
59 | req.getHeaders = () => {
60 | return req.headers;
61 | };
62 |
63 | req.connection = {};
64 |
65 | const res = new Stream();
66 | Object.defineProperty(res, "statusCode", {
67 | get() {
68 | return response.statusCode;
69 | },
70 | set(statusCode) {
71 | response.statusCode = statusCode;
72 | }
73 | });
74 | res.headers = {};
75 | res.writeHead = (status, headers) => {
76 | response.statusCode = status;
77 | if (headers) res.headers = Object.assign(res.headers, headers);
78 | };
79 | res.write = chunk => {
80 | response.body = Buffer.concat([
81 | response.body,
82 | Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
83 | ]);
84 | };
85 | res.setHeader = (name, value) => {
86 | res.headers[name] = value;
87 | };
88 | res.removeHeader = name => {
89 | delete res.headers[name];
90 | };
91 | res.getHeader = name => {
92 | return res.headers[name.toLowerCase()];
93 | };
94 | res.getHeaders = () => {
95 | return res.headers;
96 | };
97 | res.end = text => {
98 | if (text) res.write(text);
99 | response.body = Buffer.from(response.body).toString(
100 | base64Support ? "base64" : undefined
101 | );
102 | response.multiValueHeaders = res.headers;
103 | response.headers = res.headers;
104 | res.writeHead(response.statusCode);
105 | fixApiGatewayMultipleHeaders();
106 | callback(null, response);
107 | };
108 | if (event.body) {
109 | req.push(event.body, event.isBase64Encoded ? "base64" : undefined);
110 | req.push(null);
111 | }
112 |
113 | function fixApiGatewayMultipleHeaders() {
114 | for (const key of Object.keys(response.multiValueHeaders)) {
115 | if (!Array.isArray(response.multiValueHeaders[key])) {
116 | response.multiValueHeaders[key] = [response.multiValueHeaders[key]];
117 | }
118 | }
119 | }
120 |
121 | return { req, res };
122 | };
123 |
124 | // const compat = require("next-aws-lambda");
125 | const page = require("./pages/post");
126 |
127 | exports.handler = (event, context, callback) => {
128 | event.requestContext = {}
129 | const { req, res } = reqResMapper(event, callback);
130 | // event.pathにはリライトされたURLが入る(ex. /posts/2)
131 | const match = event.path.match(/posts\/(\d+)/)
132 | console.log("[render] ", event.path, match)
133 | console.log(match)
134 | if (!match) {
135 | return callback(null, {
136 | statusCode: 404,
137 | body: ''
138 | })
139 | }
140 | req.query = {
141 | id: match[1]
142 | }
143 | console.log('[header]', event.queryStringParameters, req.query, req.query['id'])
144 | page.render(req, res);
145 | };
146 |
--------------------------------------------------------------------------------