Skip to main content

Next.js for Amazon Alexa Skills

In this tutorial we'll build an Alexa skill that uses Next.js for the skill backend code.

Normally, when you build an Alexa skill the backend is a Lambda function.

Steps

First create a directory for the app and initialize it using npm init -y

mkdir alexa-nextjs
md alexa-nextjs
npm init -y

Replace the contents of the package.json with the following code.

package.json

{
"private": true,
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "node server.js"
},
"dependencies": {
"ask-sdk-core": "^2.11.0",
"ask-sdk-express-adapter": "^2.11.0",
"ask-sdk-model": "^1.37.1",
"express": "^4.17.1",
"next": "latest",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}

Run npm install

Create a file named server.js and add the following code.

server.js

const express = require('express')
const next = require('next')

// alexa stuff
require("ask-sdk-model")
const { ExpressAdapter } = require('ask-sdk-express-adapter')
const Alexa = require('ask-sdk-core')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
const server = express()

const skillBuilder = Alexa.SkillBuilders.custom();

const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speechText = 'Hello world';

return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard('Hello World', speechText)
.getResponse();
}
};

skillBuilder.addRequestHandlers(
LaunchRequestHandler
)

const skill = skillBuilder.create();

const adapter = new ExpressAdapter(skill, false, false);

server.post('/alexa', adapter.getRequestHandlers());

server.all('*', (req, res) => {
return handle(req, res)
})

server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

Create a folder named pages and add a file named pages/index.js

mkdir pages
touch pages/index.js

Add the following code to index.js

export default function Main() {
return <>
<h1>A Next.js App for Alexa</h1>
</>
}

Make a post request

Use Postman to make the following request.

{
"version": "1.0",
"session": {
"new": false,
"sessionId": "amzn1.echo-api.session.6b87c33d-e380-4e54-a133-1430aac9e45a",
"application": {
"applicationId": "amzn1.ask.skill.94cb8bf9-859f-4f99-9998-b01cf13eb388"
},
"user": {
"userId": "amzn1.ask.account.AFYD2ZWEIC4QLVQAPZBBRDRBAEB4URNY5IGKGHG6B2AEYLX3RENVE7W7Y6JOMZF54IG6EYGZP3HILOUKGL2ASOF75PEG6GNZPBQOHLPQL7BDSGJZRAJZOTSKUNMQDP3CGYKIZGDAPBFX6XI75KFYE6AFJAJYYAV4ANPXWXPXBXVGHWFK4JAEP4VB4EUDXSS4FPF4J5P4V3QCFKY"
}
},
"context": {
"System": {
"application": {
"applicationId": "amzn1.ask.skill.94cb8bf9-859f-4f99-9998-b01cf13eb388"
},
"user": {
"userId": "amzn1.ask.account.AFYD2ZWEIC4QLVQAPZBBRDRBAEB4URNY5IGKGHG6B2AEYLX3RENVE7W7Y6JOMZF54IG6EYGZP3HILOUKGL2ASOF75PEG6GNZPBQOHLPQL7BDSGJZRAJZOTSKUNMQDP3CGYKIZGDAPBFX6XI75KFYE6AFJAJYYAV4ANPXWXPXBXVGHWFK4JAEP4VB4EUDXSS4FPF4J5P4V3QCFKY"
},
"device": {
"deviceId": "amzn1.ask.device.AHQXHILO4X36DWVE7BKIMF4RZBRYWNVUCYMJXPCS6P2OTSU3LFXPPKJQ7RY35IVO7U53GVLALOUL7L7XBRTUNHGOOUAJUTPCLIW2QCXRQJDMWT32IJF355USGWEJTHUDAIXXUQ7VGYF7PDDPYCI4MOUJY4NQ",
"supportedInterfaces": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOiJodHRwczovL2FwaS5hbWF6b25hbGV4YS5jb20iLCJpc3MiOiJBbGV4YVNraWxsS2l0Iiwic3ViIjoiYW16bjEuYXNrLnNraWxsLjg3M2ZjM2YxLTMwYWEtNDdjMS04MjE3LTVjM2E3ODA4NjI2YyIsImV4cCI6MTU4MTgwMDgzMCwiaWF0IjoxNTgxODAwNTMwLCJuYmYiOjE1ODE4MDA1MzAsInByaXZhdGVDbGFpbXMiOnsiY29udGV4dCI6IkFBQUFBQUFBQUFDRld2Zyt6cWEzR1dBWGVsTTZScjdqSXdFQUFBQUFBQUJrVUFWeVU2cTU0QjhOQmRTR2FRRm9EMjZ6M0hEN3hNb0s3SVNuTG8xNGxPcXovY1dENFIvbUl3ZWFTa2t0YnVJaHlZdFRzdDc3TkRvaituSm5QVFpVRWdqNGI2Wmd2cGpUM3N6UGlZRzBkRW12R0NuSDRBV2tQWXNjOXAzbkxubEthN09BV01HRXVseC9KWmQyT25Zbk42LzlnbzloZUZzVlppNjFvQytxa3RvTGpPd0NtS2NCcmFnc3F3TGpuendmdDJEZkpsdzZnVjY5dTNFc3RBU3VpQ2ZpaXJqRWw0YnBSdXU0cmFyMDFESHgwUGxtR3MwU3NmSXhPdmVkUk00bmRQNGxENGJnTm9BcXVySWFJWDNNSjA4VDVkRlNWTlEvcGhzNDVaemdSVWUvMkNrUmNyZHgvTG9tWnZYZytTdm9id1hwZmQ3NVpoVlM0R0o2K3RkRE9Zcjg4UFl6aVlLOEx4SFJjR3Fha0dTUlliak5QSTgrYlRQLzg5bWhKVTd4aGI0PSIsImNvbnNlbnRUb2tlbiI6bnVsbCwiZGV2aWNlSWQiOiJhbXpuMS5hc2suZGV2aWNlLkFHUFc1SFc0S0oyQk5SVU5FUldKSFlBNUVNTktaU0UyS0FEVjdNQ1JQQkpTVFhRNFBFU1pYWUlPU0FWSlNXWTNGVkJHWFA2RlRSQ0ZGTlRYSlRYV09GMlU2WEdONEZMRU9XWklWWjQ1WVVKSjNGRU40R1BTNVpGUFNKT0dHV1JVN0MyNTZXRjdZQUU2VVJPQjRGTFBVVTJSV1pTQSIsInVzZXJJZCI6ImFtem4xLmFzay5hY2NvdW50LkFIR0lOS0tDWVZIRkxJWUpESVY1VlBBUFJRRVJCSVRBTlpPUlNCVVc0U09FRzVHQklNV1BKRVlRUlNCVVdSWVNYWTVIRTJTUDYzTjdVWFlRTE5SM0tTS1JMTkZOSFNQUkhIRjVJNVg2NzdGRlZSS01XT0RBQlNZVjNYQUhUSlc1RTNOQzdDQVBISVdUNjVXNkozREY0NUtCNDdaWllXSkJVR0pRUzVUTkNNUVlZSUsyTUJaVEFXWEE0RDJTVDVHQUY2S0JHVk5IVE9KQ1gzUSJ9fQ.O4UOt68MrARJc5vqMZixLzOXGlmo4L4IXAKwQUgAKTtcTL5F7XnrYAbyEkh6UbWu8Izffdf5mUmAh-q_m3Oa7YuD5yyoq_nWUc2giKGLLQ5J5yNnITCN3nOXi6AgU0Cb-ix_EFq24fsklb4Rlp-W0y36UkXI7TZxObF9BGHUJW284Q-H4v6n_tCtMtno1-uAImbLuwoaJb1fij6V8XH_iB_GIN6rvsD-HIkflsqVirdByjYYsuaR8xQ2xlnxII5eBb2iRDi1cVwuXzMsBLWFZHqCyXDtvOwZcl4OJt4E1nQ9hU175Hqu-wf7iiSptV_oIwtUY01x5xpQK0b_RvrwSg"
},
"Viewport": {
"experiences": [
{
"arcMinuteWidth": 246,
"arcMinuteHeight": 144,
"canRotate": false,
"canResize": false
}
],
"shape": "RECTANGLE",
"pixelWidth": 1024,
"pixelHeight": 600,
"dpi": 160,
"currentPixelWidth": 1024,
"currentPixelHeight": 600,
"touch": [
"SINGLE"
],
"video": {
"codecs": [
"H_264_42",
"H_264_41"
]
}
},
"Viewports": [
{
"type": "APL",
"id": "main",
"shape": "RECTANGLE",
"dpi": 160,
"presentationType": "STANDARD",
"canRotate": false,
"configuration": {
"current": {
"video": {
"codecs": [
"H_264_42",
"H_264_41"
]
},
"size": {
"type": "DISCRETE",
"pixelWidth": 1024,
"pixelHeight": 600
}
}
}
}
]
},
"request": {
"type": "LaunchRequest",
"requestId": "amzn1.echo-api.request.a6d058dd-945e-4a9f-bd9a-a9f2c0e6221f",
"timestamp": "2020-02-15T21:02:10Z",
"locale": "en-US",
"reason": "USER_INITIATED"
}
}