├── 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 |
14 | 15 | 16 | {post.title} 17 | 18 | 19 |
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 | --------------------------------------------------------------------------------