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"
}
}