diff --git a/examples/with-redis/.gitignore b/examples/with-redis/.gitignore
new file mode 100644
index 0000000000000..1437c53f70bc2
--- /dev/null
+++ b/examples/with-redis/.gitignore
@@ -0,0 +1,34 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# vercel
+.vercel
diff --git a/examples/with-redis/README.md b/examples/with-redis/README.md
new file mode 100644
index 0000000000000..f6448038ca8d2
--- /dev/null
+++ b/examples/with-redis/README.md
@@ -0,0 +1,95 @@
+# Redis Example
+
+This example showcases how to use Redis as a data store in a Next.js project. [Lambda Store](https://lambda.store/) is used as managed Redis service.
+
+The example is a basic roadmap voting application where users can enter and vote for feature requests. It features the following:
+
+- Users can add and upvote items (features in the roadmap), and enter their email addresses to be notified about the released items.
+- The API records the ip-addresses of the voters, so it does not allow multiple votes on the same item from the same IP address.
+- To find the id of any item, click the vote button, you will see its id on the url.
+
+## Demo
+
+[https://roadmap-voting-demo.vercel.app/](https://roadmap-voting-demo.vercel.app/)
+
+## Deploy your own
+
+Once you have access to [the environment variables you'll need](#configuration), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
+
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/with-redis&env=REDIS_URL&envDescription=Required%20to%20connect%20the%20app%20to%20Redis&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-redis%23configuration)
+
+## How to use
+
+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:
+
+```bash
+npx create-next-app --example with-redis with-redis-app
+# or
+yarn create next-app --example with-redis with-redis-app
+```
+
+## Configuration
+
+A data store with Redis is required for the app to work. In the steps below we'll integrate Lambda Store as the data store.
+
+### Without Vercel
+
+If you are planning to deploy your application to somewhere other than Vercel, you'll need to integrate Lambda Store by setting an environment variable.
+
+First, create an account and a database in the [Lambda Store console](https://console.lambda.store/).
+
+To connect to Redis, you will need your Redis connection string. You can get the connection string by clicking on **Connect** in the Database page within the Lambda Store dashboard as below:
+
+![setup without vercel](./docs/lstr6.png)
+
+Next, create a file called `.env.local` in the root directory and copy your connection string:
+
+```bash
+REDIS_URL="YOUR_REDIS_CONNECTION_STRING"
+```
+
+Your app is now connected to a remote Redis database!
+
+### Using Vercel
+
+You can add the Lambda Store integration to your Vercel account. Once you set up the integration you won't have to visit the Lambda Store console anymore. Follow the next steps to setup the integration:
+
+#### Step 1. Deploy Your Local Project
+
+To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example).
+
+#### Step 2. Add the Lambda Store Integration to Your Vercel Account
+
+Visit Vercel [Lambda Store Integration](https://vercel.com/integrations/lambdastore) page and click the `Add` button.
+
+#### Step 3. Configure the Integration
+
+The integration requires a [Developer API Key](howto/developerapi.md) that can be created from the [Lambda Store console](https://console.lambda.store).
+
+Enter the API key and your registered email address in the integration setup page:
+
+![setup](./docs/lstr1.png)
+
+#### Step 4. Create a Database
+
+In the next page of the integration setup, your databases will be automatically listed. A new database can be created from the Vercel Integration page as well as in the Lambda Store Console:
+
+![new db](./docs/lstr2.png)
+
+Click the **New Database**, you should be able to see the page below:
+
+![new db form](./docs/lstr3.png)
+
+Fill out the form and click on **Create** to have your new database.
+
+#### Step 5. Link the Database to Your Project
+
+Select your project from the dropdown menu then click on **Link To Project** for any database.
+
+`REDIS_URL` will be automatically set as an environment variable for your application.
+
+![link project](./docs/lstr4.png)
+
+![redis url env](./docs/lstr5.png)
+
+**Important:** You will need to re-deploy your application for the change to be effective.
diff --git a/examples/with-redis/docs/lstr1.png b/examples/with-redis/docs/lstr1.png
new file mode 100644
index 0000000000000..b71694d33918b
Binary files /dev/null and b/examples/with-redis/docs/lstr1.png differ
diff --git a/examples/with-redis/docs/lstr2.png b/examples/with-redis/docs/lstr2.png
new file mode 100644
index 0000000000000..13d377af009bd
Binary files /dev/null and b/examples/with-redis/docs/lstr2.png differ
diff --git a/examples/with-redis/docs/lstr3.png b/examples/with-redis/docs/lstr3.png
new file mode 100644
index 0000000000000..0a4df68aa10b8
Binary files /dev/null and b/examples/with-redis/docs/lstr3.png differ
diff --git a/examples/with-redis/docs/lstr4.png b/examples/with-redis/docs/lstr4.png
new file mode 100644
index 0000000000000..a18197286db10
Binary files /dev/null and b/examples/with-redis/docs/lstr4.png differ
diff --git a/examples/with-redis/docs/lstr5.png b/examples/with-redis/docs/lstr5.png
new file mode 100644
index 0000000000000..55f60d119ebc7
Binary files /dev/null and b/examples/with-redis/docs/lstr5.png differ
diff --git a/examples/with-redis/docs/lstr6.png b/examples/with-redis/docs/lstr6.png
new file mode 100644
index 0000000000000..488123715f901
Binary files /dev/null and b/examples/with-redis/docs/lstr6.png differ
diff --git a/examples/with-redis/package.json b/examples/with-redis/package.json
new file mode 100644
index 0000000000000..5219a55712d37
--- /dev/null
+++ b/examples/with-redis/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "with-redis",
+ "version": "0.1.0",
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "next": "latest",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "react-toastify": "^6.0.8",
+ "redis": "^3.0.2",
+ "uuid": "^8.2.0"
+ },
+ "license": "MIT"
+}
diff --git a/examples/with-redis/pages/_app.js b/examples/with-redis/pages/_app.js
new file mode 100644
index 0000000000000..2e98a5ba8e3f8
--- /dev/null
+++ b/examples/with-redis/pages/_app.js
@@ -0,0 +1,7 @@
+import 'react-toastify/dist/ReactToastify.css'
+import '../styles/base.css'
+
+// This default export is required in a new `pages/_app.js` file.
+export default function MyApp({ Component, pageProps }) {
+ return
+}
diff --git a/examples/with-redis/pages/api/addemail.js b/examples/with-redis/pages/api/addemail.js
new file mode 100644
index 0000000000000..17ec4134b3ca7
--- /dev/null
+++ b/examples/with-redis/pages/api/addemail.js
@@ -0,0 +1,34 @@
+import redis from 'redis'
+import { promisify } from 'util'
+
+export default async function addEmail(req, res) {
+ const client = redis.createClient({
+ url: process.env.REDIS_URL,
+ })
+ const saddAsync = promisify(client.sadd).bind(client)
+
+ const body = req.body
+ const email = body['email']
+
+ client.on('error', function (err) {
+ throw err
+ })
+
+ if (email && validateEmail(email)) {
+ await saddAsync('emails', email)
+ client.quit()
+ res.json({
+ body: 'success',
+ })
+ } else {
+ client.quit()
+ res.json({
+ error: 'Invalid email',
+ })
+ }
+}
+
+function validateEmail(email) {
+ const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+ return re.test(String(email).toLowerCase())
+}
diff --git a/examples/with-redis/pages/api/create.js b/examples/with-redis/pages/api/create.js
new file mode 100644
index 0000000000000..4cff017f7722d
--- /dev/null
+++ b/examples/with-redis/pages/api/create.js
@@ -0,0 +1,33 @@
+import redis from 'redis'
+import { promisify } from 'util'
+import { v4 as uuidv4 } from 'uuid'
+
+export default async function create(req, res) {
+ const client = redis.createClient({
+ url: process.env.REDIS_URL,
+ })
+ const hsetAsync = promisify(client.hset).bind(client)
+ const zaddAsync = promisify(client.zadd).bind(client)
+
+ const body = req.body
+ const title = body['title']
+ const id = uuidv4()
+
+ client.on('error', function (err) {
+ throw err
+ })
+
+ if (title) {
+ await zaddAsync('roadmap', 0, id)
+ await hsetAsync(id, 'title', title)
+ client.quit()
+ res.json({
+ body: 'success',
+ })
+ } else {
+ client.quit()
+ res.json({
+ error: 'Feature can not be empty',
+ })
+ }
+}
diff --git a/examples/with-redis/pages/api/list.js b/examples/with-redis/pages/api/list.js
new file mode 100644
index 0000000000000..892416f5f9fd8
--- /dev/null
+++ b/examples/with-redis/pages/api/list.js
@@ -0,0 +1,33 @@
+import redis from 'redis'
+import { promisify } from 'util'
+
+export default async function list(req, res) {
+ const client = redis.createClient({
+ url: process.env.REDIS_URL,
+ })
+ const hgetallAsync = promisify(client.hgetall).bind(client)
+ const zrevrangeAsync = promisify(client.zrevrange).bind(client)
+
+ let n = await zrevrangeAsync('roadmap', 0, 50, 'WITHSCORES')
+ let result = []
+ const promises = []
+ for (let i = 0; i < n.length - 1; i += 2) {
+ let id = n[i]
+ let p = hgetallAsync(id).then((item) => {
+ if (item) {
+ item['id'] = id
+ item['score'] = n[i + 1]
+ result.push(item)
+ }
+ })
+ promises.push(p)
+ }
+
+ await Promise.all(promises).then(() => {
+ client.quit()
+ })
+
+ res.json({
+ body: result,
+ })
+}
diff --git a/examples/with-redis/pages/api/vote.js b/examples/with-redis/pages/api/vote.js
new file mode 100644
index 0000000000000..141aad0b18454
--- /dev/null
+++ b/examples/with-redis/pages/api/vote.js
@@ -0,0 +1,30 @@
+import redis from 'redis'
+import { promisify } from 'util'
+
+export default async function list(req, res) {
+ const client = redis.createClient({
+ url: process.env.REDIS_URL,
+ })
+ client.on('error', function (err) {
+ throw err
+ })
+
+ const body = req.body
+ const id = body['id']
+ let ip = req.headers['x-forwarded-for']
+ const saddAsync = promisify(client.sadd).bind(client)
+ let c = await saddAsync('s:' + id, ip ? ip : '-')
+ if (c === 0) {
+ client.quit()
+ res.json({
+ error: 'You can not vote an item multiple times',
+ })
+ } else {
+ const zincrbyAsync = promisify(client.zincrby).bind(client)
+ let v = await zincrbyAsync('roadmap', 1, id)
+ client.quit()
+ res.json({
+ body: v,
+ })
+ }
+}
diff --git a/examples/with-redis/pages/index.js b/examples/with-redis/pages/index.js
new file mode 100644
index 0000000000000..ff81c2fc73b3a
--- /dev/null
+++ b/examples/with-redis/pages/index.js
@@ -0,0 +1,168 @@
+import Head from 'next/head'
+import { ToastContainer, toast } from 'react-toastify'
+import React, { useState, useRef, useEffect } from 'react'
+
+function Home() {
+ const [items, setItems] = useState(0)
+ const inputNewFeature = useRef()
+ const inputEmail = useRef()
+
+ useEffect(() => {
+ refreshData()
+ }, [])
+
+ function refreshData() {
+ fetch('api/list')
+ .then((res) => res.json())
+ .then(
+ (result) => {
+ setItems(result.body)
+ inputNewFeature.current.value = ''
+ inputEmail.current.value = ''
+ },
+ (error) => {
+ setItems([])
+ }
+ )
+ }
+
+ function handleNewFeature(event) {
+ const requestOptions = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ title: inputNewFeature.current.value }),
+ }
+ fetch('api/create', requestOptions)
+ .then((response) => response.json())
+ .then((data) => {
+ if (data.error) {
+ toast.error(data.error, { hideProgressBar: true, autoClose: 3000 })
+ } else {
+ toast.info('Your feature has been added to the list.', {
+ hideProgressBar: true,
+ autoClose: 3000,
+ })
+ refreshData()
+ }
+ })
+ event.preventDefault()
+ }
+
+ function handleNewEmail(event) {
+ const requestOptions = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email: inputEmail.current.value }),
+ }
+ console.log(requestOptions)
+ fetch('api/addemail', requestOptions)
+ .then((response) => response.json())
+ .then((data) => {
+ if (data.error) {
+ toast.error(data.error, { hideProgressBar: true, autoClose: 3000 })
+ } else {
+ toast.info('Your email has been added to the list.', {
+ hideProgressBar: true,
+ autoClose: 3000,
+ })
+ refreshData()
+ }
+ })
+ event.preventDefault()
+ }
+
+ function vote(event, id) {
+ const requestOptions = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ id: id }),
+ }
+ console.log(requestOptions)
+ fetch('api/vote', requestOptions)
+ .then((response) => response.json())
+ .then((data) => {
+ console.log(data)
+ if (data.error) {
+ toast.error(data.error, { hideProgressBar: true, autoClose: 3000 })
+ } else {
+ refreshData()
+ }
+ })
+ }
+
+ return (
+
+
+ Roadmap Voting
+
+
+
+
+
+
+
+
+
+ Help us by voting our roadmap.
+ ▲
+ Vote up the features you want to see in the next release.
+