cyrilwanner / next-serverless

☁️ next-serverless deploys your next.js application to AWS Lambda with minimal or even no configuration.
MIT License
81 stars 7 forks source link

ApiGateway Hostnames Auto-Prefix Does Not Seem to Work #6

Closed eeyang92 closed 5 years ago

eeyang92 commented 5 years ago

Hi, I'm follow this tutorial, but have made a few modifications (next-serverless being one of them): https://statsbot.co/blog/a-crash-course-on-serverless-side-rendering-with-reactjs-nextjs-and-aws-lambda/

I believe I am using the Link component correctly, as shown below:

import React from 'react'
import Link from 'next-serverless/link'

export default () => (
  <nav className='nav'>
    <ul>
      <li>
        <Link href='/'><a>Home</a></Link>
      </li>
      <li>
        <Link href='/dogs'><a>Dogs</a></Link>
      </li>
      <li>
        <Link href='/dogs/shepherd'><a>Only Shepherds</a></Link>
      </li>
    </ul>
  </nav>
)

However, when I click the links in my production lambda, it does not add the prefix and I get a forbidden error message. If I manually type in the URL then it correctly displays the content.

cyrilwanner commented 5 years ago

Hi @eeyang92 Yes, this code also looks correct to me.

In the tutorial you followed, they create a custom server for handling the requests. Did you also do that? If yes, can you maybe post the code of your custom server? Because next-serverless sets the prefix within the server implementation and then reads it from there in the link. So if the link looks correct, maybe the prefix never gets set in the server. The implementation with a custom server may not be that easy if you have a lot of logic or even other plugins in it.

eeyang92 commented 5 years ago

@cyrilwanner, sorry for the late response, I've been playing around with different configurations, and when I took a look at your source code - it finally became clear what I was doing wrong.

Here is my current setup:

// Copied from https://statsbot.co/blog/a-crash-course-on-serverless-side-rendering-with-reactjs-nextjs-and-aws-lambda/

// server.js
const express = require('express')
const path = require('path')
const dev = process.env.NODE_ENV !== 'production'
const next = require('next')
const pathMatch = require('path-match')
const app = next({ dev })
const handle = app.getRequestHandler()
const { parse } = require('url')

const server = express()
const route = pathMatch()
server.use('/_next', express.static(path.join(__dirname, '.next')))
server.get('/', (req, res) => app.render(req, res, '/'))
server.get('/dogs', (req, res) => app.render(req, res, '/dogs'))
server.get('/dogs/:breed', (req, res) => {
  const params = route('/dogs/:breed')(parse(req.url).pathname)
  return app.render(req, res, '/dogs/_breed', params)
})
server.get('*', (req, res) => handle(req, res))

module.exports.app = app // Slightly modified here
module.exports.server = server
// index.js
const nextServerless = require('next-serverless/handler')

const { app, server } = require('./server')

module.exports.handler = nextServerless(app, server, () => {
    server.listen(3000, () => {
        console.log('> Ready on http://localhost:3000')
    })
})

With this configuration, all express routing as shown in the example works! Link is also working as expected.

However, one thing not working is that the local dev environment: next-serverless does not respect runLocal: () => void. Looking at the source code, I see:

// from https://github.com/cyrilwanner/next-serverless/blob/master/src/next/server.js

...
module.exports.handler = nextServerless(app, handler, () => {
  // create a normal http node server for local usage
  createServer(handler).listen(argv.port, argv.hostname, (err) => {
    if (err) throw err;
    console.log(`> Ready on http://${argv.hostname || 'localhost'}:${argv.port}`);
  });
});

Because it is using the http server rather than my express server, the custom routing isn't quite working. I believe the fix would be to detect if runLocal is provided, then use that; otherwise run the default server as you've written above.

cyrilwanner commented 5 years ago

Hi @eeyang92
The code which you found with the http server is only the fallback code when no custom server is set up. If a custom server is used (like you do), you will never reach this code but end up here: https://github.com/cyrilwanner/next-serverless/blob/master/src/next/handler.js#L54 The isLambda() will return false (because it runs locally) and so the runLocal function will handle everything. You actually define and start your local server within this param, so from the code I see, your setup looks correct and I can't find something wrong.

So I guess we have to debug it a bit to hopefully find the error. Did you try to check if the runLocal function actually gets called (e.g. with a unique console.log or by throwing an error)? If yes and when running locally, which links/routes are working and which are not? Do you get any error or other information in the console?

eeyang92 commented 5 years ago

Ah, I see, indeed your package does export handler, not server.

I did get it working - it turns out I had replaced my scripts with next-serverless instead of running node index.js on the custom server, so my index.js script was never being called in the first place. I probably would have realized that earlier if I had changed the console.log to something else!

Thanks for your help in resolving this issue! I created a repo to demonstrate a working configuration (with typescript): https://github.com/eeyang92/next-serverless-boilerplate

I'm sure it can be refactored, but for now, I'm happy it's working.

cyrilwanner commented 5 years ago

I'm glad that it is working now :) Thank you for the boilerplate, this is a perfect way to start a new project!