2018-02-27 13:16:17 +01:00
# Running Next.JS and React /inside/ of ActionHero
2019-03-07 17:40:08 +01:00
This server will render dynamic next.js/react pages on some routes, and normal ActionHero API requests on others.< br >
2018-02-27 13:16:17 +01:00
This configuration works with both Next and ActionHero hot reloading of code.
A more detailed example showcasing how to use fetch and web sockets to interact with your API can be found here: https://github.com/actionhero/next-in-actionhero
2018-04-03 14:19:05 +02:00
## How to use
2018-02-27 13:16:17 +01:00
2018-04-03 14:19:05 +02:00
### Using `create-next-app`
2018-02-27 13:16:17 +01:00
2020-05-27 23:51:11 +02:00
Execute [`create-next-app` ](https://github.com/vercel/next.js/tree/canary/packages/create-next-app ) with [npm ](https://docs.npmjs.com/cli/init ) or [Yarn ](https://yarnpkg.com/lang/en/docs/cli/create/ ) to bootstrap the example:
2018-02-27 13:16:17 +01:00
2018-04-03 14:19:05 +02:00
```bash
2020-01-16 23:23:56 +01:00
npm init next-app --example custom-server-actionhero custom-server-actionhero-app
2018-04-03 14:19:05 +02:00
# or
yarn create next-app --example custom-server-actionhero custom-server-actionhero-app
```
### Download manually
2018-07-11 23:56:15 +02:00
Download the example:
2018-04-03 14:19:05 +02:00
```bash
2020-05-27 20:11:39 +02:00
curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/custom-server-actionhero
2018-04-03 14:19:05 +02:00
cd custom-server-actionhero
```
Install it and run:
```bash
npm install
npm run start
# or
yarn
yarn start
```
2018-02-27 13:16:17 +01:00
## How does this work?
1. Create an initializer to load next.js and create a handler that can extract the normal node `req` and `res` from the connection
```js
// initializers/next.js
2019-05-29 13:57:26 +02:00
const { Initializer, api } = require('actionhero')
2018-02-27 13:16:17 +01:00
const next = require('next')
module.exports = class NextInitializer extends Initializer {
2019-05-29 13:57:26 +02:00
constructor() {
2018-02-27 13:16:17 +01:00
super()
this.name = 'next'
}
2019-05-29 13:57:26 +02:00
async initialize() {
2018-02-27 13:16:17 +01:00
api.next = {
2020-05-18 21:24:37 +02:00
render: async (connection) => {
2019-05-29 13:57:26 +02:00
if (connection.type !== 'web') {
throw new Error('Connections for NEXT apps must be of type "web"')
}
2018-02-27 13:16:17 +01:00
const req = connection.rawConnection.req
const res = connection.rawConnection.res
return api.next.handle(req, res)
2019-05-29 13:57:26 +02:00
},
2018-02-27 13:16:17 +01:00
}
2019-05-29 13:57:26 +02:00
api.next.dev = api.env === 'development'
if (api.next.dev) {
api.log('Running next in development mode...')
}
2018-02-27 13:16:17 +01:00
2019-05-29 13:57:26 +02:00
api.next.app = next({ dev: api.next.dev })
2018-02-27 13:16:17 +01:00
api.next.handle = api.next.app.getRequestHandler()
await api.next.app.prepare()
}
2019-05-29 13:57:26 +02:00
async stop() {
2018-02-27 13:16:17 +01:00
await api.next.app.close()
}
}
```
2019-05-29 13:57:26 +02:00
2. Create an action which will run the above `api.next.render(connection)` . Note that we will not be relying on ActionHero to respond to the client's request in this case, and leave that up to next (via: `data.toRender = false` )
2018-02-27 13:16:17 +01:00
```js
// actions/next.js
2019-05-29 13:57:26 +02:00
const { Action, api } = require('actionhero')
2018-02-27 13:16:17 +01:00
module.exports = class CreateChatRoom extends Action {
2019-05-29 13:57:26 +02:00
constructor() {
2018-02-27 13:16:17 +01:00
super()
this.name = 'render'
this.description = 'I render the next.js react website'
}
2019-05-29 13:57:26 +02:00
async run(data) {
2018-02-27 13:16:17 +01:00
data.toRender = false
return api.next.render(data.connection)
}
}
```
2019-05-29 13:57:26 +02:00
3. Tell ActionHero to use the api rather than the file server as the top-level route in `api.config.servers.web.rootEndpointType = 'api'` . This will allows "/" to listen to API requests. Also update `api.config.general.paths.public = [ path.join(__dirname, '/../static') ]` . In this configuration, the next 'static' renderer will take priority over the ActionHero 'public file' api. Note that any static assets (CSS, fonts, etc) will need to be in "./static" rather than "./public".
2018-02-27 13:16:17 +01:00
2020-03-28 18:57:19 +01:00
Note that this is where the websocket server, if you enable it, will place the `ActionheroWebsocketClient` library.< br >
2018-02-27 13:16:17 +01:00
4. Configure a wild-card route at the lowest priority of your GET handler to catch all web requests that aren't caught by other actions:
```js
// config/routes.js
exports['default'] = {
2020-05-18 21:24:37 +02:00
routes: (api) => {
2018-02-27 13:16:17 +01:00
return {
get: [
{ path: '/time', action: 'time' },
2019-05-29 13:57:26 +02:00
{ path: '/', matchTrailingPathParts: true, action: 'render' },
],
2018-02-27 13:16:17 +01:00
}
2019-05-29 13:57:26 +02:00
},
2018-02-27 13:16:17 +01:00
}
```