Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does nextjs getServerSideProps have issues with cacheable-response? #64

Closed
joshuaellis opened this issue Nov 27, 2020 · 2 comments
Closed

Comments

@joshuaellis
Copy link

We're running a custom server next site on AWS Lightsail (2 GB RAM, 1 vCPU) and to improve performance we implemented cacheable-response based on the updated example version #18786. But we had large issues with the Node server choking on 50 users. I know it's very vague, but I'm wondering if it was something to do with our custom server config in conjunction with cacheable-response with getServerSideProps. I posted here but no resposne, and was just wondering if you might know or an issue or can see an issue.

server.js

const express = require('express')
const next = require('next')
const compression = require('compression')
const cacheableResponse = require('cacheable-response')

const port = process.env.PORT || 3000
const dev = process.env.NODE_ENV_CUSTOM === 'dev'

const app = next({ dev })
const handle = app.getRequestHandler()

const CACHE_MAX_AGE = dev
  ? 1 // for dev, to disable caching
  : 1000 * 60 * 60 // for !dev, 1hr

app
  .prepare()
  .then(() => {
    const server = express()
    server.enable('strict routing')

    server.use(compression())

    // Caching
    // Disable on _next files
    server.get('/_next/*', (req, res) => handle(req, res))

    // Disable on static files
    server.get('/static/*', (req, res) => handle(req, res))

    // Disable on API endpoints within NextJS
    server.all('/api/*', (req, res) => handle(req, res))

    server.all('*', (req, res) => {
      // NextJS issue with handling URL /_error
      // https://github.com/vercel/next.js/issues/9443
      if (req.originalUrl === '/_error') {
        res.redirect(301, '/page-not-found')
      }

      return ssrCache(req, res)
    })

    server.listen(port, err => {
      if (err) throw err
      console.log(`> Ready on port ${port} 💁🏻‍♀️`)
    })
  })
  .catch(ex => {
    console.error(ex.stack)
    process.exit(1)
  })

const ssrCache = cacheableResponse({
  ttl: CACHE_MAX_AGE,
  get: async ({ req, res }) => {
    const rawResEnd = res.end
    const data = await new Promise(resolve => {
      res.end = payload => resolve(payload)
      app.render(req, res, req.path, {
        ...req.query,
        ...req.params
      })
    })
    res.end = rawResEnd
    return { data }
  },
  send: ({ data, res }) => res.send(data)
})

_document.js

export default class SennepDocument extends Document {
  static async getInitialProps (ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      }
    } finally {
      sheet.seal()
    }
  }

  render () {
    return (
      <Html lang='en'>
        <Head>
          <style
            dangerouslySetInnerHTML={{
              __html: `
            ${CSS_FONTS}
            ${CSS_GLOBAL}
          `
            }}
          />
          <TrackingGtmHead />
        </Head>
        <body>
          <TrackingGtmBody />
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

_app.js

export default class CustomApp extends App {
  static propTypes = {
    carouselData: PropTypes.arrayOf(PropTypes.object).isRequired
  }

  static defaultProps = {
    carouselData: []
  }

  static async getInitialProps () {
    let mutatedApiData = null

    try {
      const apiData = await apiGetStaticRoute('carousel')
      if (apiData.success) {
        mutatedApiData = appGenerateCarousel(apiData.response.carousel)
      } else {
        throw apiData.response
      }
      return {
        ...mutatedApiData
      }
    } catch ({ message, code }) {
      return {
        error: { message, code }
      }
    }
  }

  render () {
    const { Component, pageProps, carouselData } = this.props

    return (
      <div>
        <Provider store={store}>
          <SitePageTransitioner>
            <Component
              {...pageProps}
              carouselData={carouselData}
            />
          </SitePageTransitioner>
        </Provider>
      </div>
    )
  }
}

index.js

export default class PageHome extends React.PureComponent {
  static propTypes = {
    carouselData: PropTypes.arrayOf(PropTypes.object).isRequired,
    error: PropTypes.shape({
      code: PropTypes.number,
      message: PropTypes.string
    }),
    modules: PropTypes.array.isRequired
  }

  static defaultProps = {
    carouselData: [],
    modules: []
  }

  render () {
    const {
      carouselData,
      error,
      modules
    } = this.props

    if (error) {
      console.error(error)
      return <Error statusCode={error.code} message={error.message} />
    }

    return (
      <Home>
        <SiteModuleContainer
          header={{
            type: 'home',
            backgroundColor: COLOURS.yellow,
            theme: MODULE_THEMES_LIGHT
          }}
          modules={[...carouselData, ...modules]}
          footer={{
            backgroundColor: COLOURS.yellow,
            theme: MODULE_THEMES_LIGHT
          }}
        />
      </Home>
    )
  }
}

export async function getServerSideProps ({ query }) {
  try {
    const apiData = await apiGetStaticRoute('home', query)
    if (apiData.success) {
      const data = pageGenerateGenericPage(apiData.response)
      return {
        props: data
      }
    } else {
      throw apiData.response
    }
  } catch ({ message, code }) {
    return {
      props: {
        error: { message, code }
      }
    }
  }
}
@Kikobeats
Copy link
Owner

I'm not using Next.js with cacheable-response, so I can't say too much more.

cacheable-response is a generic cache layer library. Although it can be connected with any codebase (or at least that's the intention), probably Next.js internal API is too opinionated and susceptible to change in a short period of time, making the job really hard.

If you are still facing with this, consider take a look to https://github.com/rjyo/next-boost.

@joshuaellis
Copy link
Author

No worries, I think you're right looking at #63. Thank you for replying though and for pointing me in another direction 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants