mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Merge branch 'v3-beta'
This commit is contained in:
commit
daf8d5bf95
39
.github/issue_template.md
vendored
Normal file
39
.github/issue_template.md
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!--- Provide a general summary of the issue in the Title above -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you very much for contributing to Next.js by creating an issue! ❤️
|
||||||
|
To avoid duplicate issues we ask you to check off the following list
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Checked checkbox should look like this: [x] -->
|
||||||
|
- [ ] I have searched the [issues](https://github.com/zeit/next.js/issues) of this repository and believe that this is not a duplicate.
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
<!--- If you're describing a bug, tell us what should happen -->
|
||||||
|
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||||
|
|
||||||
|
## Current Behavior
|
||||||
|
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||||
|
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||||
|
|
||||||
|
## Steps to Reproduce (for bugs)
|
||||||
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||||
|
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||||
|
|
||||||
|
## Your Environment
|
||||||
|
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||||
|
| Tech | Version |
|
||||||
|
|---------|---------|
|
||||||
|
| next | |
|
||||||
|
| node | |
|
||||||
|
| OS | |
|
||||||
|
| browser | |
|
||||||
|
| etc | |
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -4,12 +4,15 @@ dist
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
# logs
|
# logs
|
||||||
npm-debug.log
|
*.log
|
||||||
|
|
||||||
# coverage
|
# coverage
|
||||||
.nyc_output
|
.nyc_output
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
|
# test output
|
||||||
|
test/**/out
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
@ -8,7 +8,6 @@ addons:
|
||||||
- google-chrome-stable
|
- google-chrome-stable
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "4"
|
|
||||||
- "6"
|
- "6"
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- nodejs_version: "4"
|
|
||||||
- nodejs_version: "6"
|
- nodejs_version: "6"
|
||||||
|
|
||||||
# Install scripts. (runs after repo cloning)
|
# Install scripts. (runs after repo cloning)
|
||||||
|
|
3
bin/next
3
bin/next
|
@ -12,7 +12,7 @@ if (pkg.peerDependencies) {
|
||||||
// When 'npm link' is used it checks the clone location. Not the project.
|
// When 'npm link' is used it checks the clone location. Not the project.
|
||||||
require.resolve(dependency)
|
require.resolve(dependency)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`${dependency} not found. Please install ${dependency} using 'npm install ${dependency}'`)
|
console.warn(`The module '${dependency}' was not found. Next.js requires that you include it in 'dependencies' of your 'package.json'. To add it, run 'npm install --save ${dependency}'`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ const commands = new Set([
|
||||||
'init',
|
'init',
|
||||||
'build',
|
'build',
|
||||||
'start',
|
'start',
|
||||||
|
'export',
|
||||||
defaultCommand
|
defaultCommand
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
11
bin/next-dev
11
bin/next-dev
|
@ -12,12 +12,12 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development'
|
||||||
const argv = parseArgs(process.argv.slice(2), {
|
const argv = parseArgs(process.argv.slice(2), {
|
||||||
alias: {
|
alias: {
|
||||||
h: 'help',
|
h: 'help',
|
||||||
|
H: 'hostname',
|
||||||
p: 'port'
|
p: 'port'
|
||||||
},
|
},
|
||||||
boolean: ['h'],
|
boolean: ['h'],
|
||||||
default: {
|
string: ['H'],
|
||||||
p: 3000
|
default: { p: 3000 }
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (argv.help) {
|
if (argv.help) {
|
||||||
|
@ -35,6 +35,7 @@ if (argv.help) {
|
||||||
|
|
||||||
Options
|
Options
|
||||||
--port, -p A port number on which to start the application
|
--port, -p A port number on which to start the application
|
||||||
|
--hostname, -H Hostname on which to start the application
|
||||||
--help, -h Displays this message
|
--help, -h Displays this message
|
||||||
`)
|
`)
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
|
@ -56,10 +57,10 @@ if (!existsSync(join(dir, 'pages'))) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const srv = new Server({ dir, dev: true })
|
const srv = new Server({ dir, dev: true })
|
||||||
srv.start(argv.port)
|
srv.start(argv.port, argv.hostname)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
if (!process.env.NOW) {
|
if (!process.env.NOW) {
|
||||||
console.log(`> Ready on http://localhost:${argv.port}`)
|
console.log(`> Ready on http://${argv.hostname ? argv.hostname : 'localhost'}:${argv.port}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
67
bin/next-export
Normal file
67
bin/next-export
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import { resolve, join } from 'path'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import parseArgs from 'minimist'
|
||||||
|
import exportApp from '../server/export'
|
||||||
|
import { printAndExit } from '../lib/utils'
|
||||||
|
|
||||||
|
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
|
||||||
|
|
||||||
|
const argv = parseArgs(process.argv.slice(2), {
|
||||||
|
alias: {
|
||||||
|
h: 'help',
|
||||||
|
s: 'silent',
|
||||||
|
o: 'outdir'
|
||||||
|
},
|
||||||
|
boolean: ['h'],
|
||||||
|
default: {
|
||||||
|
s: false,
|
||||||
|
o: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (argv.help) {
|
||||||
|
console.log(`
|
||||||
|
Description
|
||||||
|
Exports the application for production deployment
|
||||||
|
|
||||||
|
Usage
|
||||||
|
$ next export [options] <dir>
|
||||||
|
|
||||||
|
<dir> represents where the compiled dist folder should go.
|
||||||
|
If no directory is provided, the dist folder will be created in the current directory.
|
||||||
|
You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration, otherwise it will be created inside '.next'
|
||||||
|
|
||||||
|
Options
|
||||||
|
-h - list this help
|
||||||
|
-o - set the output dir (defaults to 'out')
|
||||||
|
-s - do not print any messages to console
|
||||||
|
`)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dir = resolve(argv._[0] || '.')
|
||||||
|
|
||||||
|
// Check if pages dir exists and warn if not
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
printAndExit(`> No such directory exists as the project root: ${dir}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existsSync(join(dir, 'pages'))) {
|
||||||
|
if (existsSync(join(dir, '..', 'pages'))) {
|
||||||
|
printAndExit('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?')
|
||||||
|
}
|
||||||
|
|
||||||
|
printAndExit('> Couldn\'t find a `pages` directory. Please create one under the project root')
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
silent: argv.silent,
|
||||||
|
outdir: argv.outdir ? resolve(argv.outdir) : resolve(dir, 'out')
|
||||||
|
}
|
||||||
|
|
||||||
|
exportApp(dir, options)
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
|
@ -3,8 +3,6 @@
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import parseArgs from 'minimist'
|
import parseArgs from 'minimist'
|
||||||
import Server from '../server'
|
import Server from '../server'
|
||||||
import { existsSync } from 'fs'
|
|
||||||
import getConfig from '../server/config'
|
|
||||||
|
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
|
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
|
||||||
|
|
||||||
|
@ -47,15 +45,8 @@ if (argv.help) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dir = resolve(argv._[0] || '.')
|
const dir = resolve(argv._[0] || '.')
|
||||||
const dist = getConfig(dir).distDir
|
|
||||||
|
|
||||||
const srv = new Server({ dir })
|
const srv = new Server({ dir })
|
||||||
|
|
||||||
if (!existsSync(resolve(dir, dist, 'BUILD_ID'))) {
|
|
||||||
console.error(`> Could not find a valid build in the '${dist}' directory! Try building your app with 'next build' before starting the server.`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.start(argv.port, argv.hostname)
|
srv.start(argv.port, argv.hostname)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (!process.env.NOW) {
|
if (!process.env.NOW) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { createElement } from 'react'
|
import { createElement } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import mitt from 'mitt'
|
|
||||||
import HeadManager from './head-manager'
|
import HeadManager from './head-manager'
|
||||||
import { createRouter } from '../lib/router'
|
import { createRouter } from '../lib/router'
|
||||||
|
import EventEmitter from '../lib/EventEmitter'
|
||||||
import App from '../lib/app'
|
import App from '../lib/app'
|
||||||
import { loadGetInitialProps, getURL } from '../lib/utils'
|
import { loadGetInitialProps, getURL } from '../lib/utils'
|
||||||
import ErrorDebugComponent from '../lib/error-debug'
|
import ErrorDebugComponent from '../lib/error-debug'
|
||||||
|
@ -24,6 +24,7 @@ const {
|
||||||
pathname,
|
pathname,
|
||||||
query,
|
query,
|
||||||
buildId,
|
buildId,
|
||||||
|
chunks,
|
||||||
assetPrefix
|
assetPrefix
|
||||||
},
|
},
|
||||||
location
|
location
|
||||||
|
@ -37,7 +38,13 @@ window.__NEXT_LOADED_PAGES__.forEach(({ route, fn }) => {
|
||||||
})
|
})
|
||||||
delete window.__NEXT_LOADED_PAGES__
|
delete window.__NEXT_LOADED_PAGES__
|
||||||
|
|
||||||
|
window.__NEXT_LOADED_CHUNKS__.forEach(({ chunkName, fn }) => {
|
||||||
|
pageLoader.registerChunk(chunkName, fn)
|
||||||
|
})
|
||||||
|
delete window.__NEXT_LOADED_CHUNKS__
|
||||||
|
|
||||||
window.__NEXT_REGISTER_PAGE = pageLoader.registerPage.bind(pageLoader)
|
window.__NEXT_REGISTER_PAGE = pageLoader.registerPage.bind(pageLoader)
|
||||||
|
window.__NEXT_REGISTER_CHUNK = pageLoader.registerChunk.bind(pageLoader)
|
||||||
|
|
||||||
const headManager = new HeadManager()
|
const headManager = new HeadManager()
|
||||||
const appContainer = document.getElementById('__next')
|
const appContainer = document.getElementById('__next')
|
||||||
|
@ -49,6 +56,11 @@ export let ErrorComponent
|
||||||
let Component
|
let Component
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
|
// Wait for all the dynamic chunks to get loaded
|
||||||
|
for (const chunkName of chunks) {
|
||||||
|
await pageLoader.waitForChunk(chunkName)
|
||||||
|
}
|
||||||
|
|
||||||
ErrorComponent = await pageLoader.loadPage('/_error')
|
ErrorComponent = await pageLoader.loadPage('/_error')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -65,7 +77,7 @@ export default async () => {
|
||||||
err
|
err
|
||||||
})
|
})
|
||||||
|
|
||||||
const emitter = mitt()
|
const emitter = new EventEmitter()
|
||||||
|
|
||||||
router.subscribe(({ Component, props, hash, err }) => {
|
router.subscribe(({ Component, props, hash, err }) => {
|
||||||
render({ Component, props, err, hash, emitter })
|
render({ Component, props, err, hash, emitter })
|
||||||
|
|
|
@ -42,8 +42,10 @@ ReactReconciler.mountComponent = function (...args) {
|
||||||
try {
|
try {
|
||||||
return originalMountComponent(...args)
|
return originalMountComponent(...args)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next.renderError(err)
|
if (!err.abort) {
|
||||||
err.abort = true
|
next.renderError(err)
|
||||||
|
err.abort = true
|
||||||
|
}
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,19 @@ export default () => {
|
||||||
async function ping () {
|
async function ping () {
|
||||||
try {
|
try {
|
||||||
const url = `/_next/on-demand-entries-ping?page=${Router.pathname}`
|
const url = `/_next/on-demand-entries-ping?page=${Router.pathname}`
|
||||||
const res = await fetch(url)
|
const res = await fetch(url, {
|
||||||
|
credentials: 'same-origin'
|
||||||
|
})
|
||||||
const payload = await res.json()
|
const payload = await res.json()
|
||||||
if (payload.invalid) {
|
if (payload.invalid) {
|
||||||
location.reload()
|
// Payload can be invalid even if the page is not exists.
|
||||||
|
// So, we need to make sure it's exists before reloading.
|
||||||
|
const pageRes = await fetch(location.href, {
|
||||||
|
credentials: 'same-origin'
|
||||||
|
})
|
||||||
|
if (pageRes.status === 200) {
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error with on-demand-entries-ping: ${err.message}`)
|
console.error(`Error with on-demand-entries-ping: ${err.message}`)
|
||||||
|
|
|
@ -32,17 +32,22 @@ export default () => {
|
||||||
|
|
||||||
const { err, Component } = Router.components[route] || {}
|
const { err, Component } = Router.components[route] || {}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// reload to recover from runtime errors
|
||||||
|
Router.reload(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Router.route !== route) {
|
||||||
|
// If this is a not a change for a currently viewing page.
|
||||||
|
// We don't need to worry about it.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!Component) {
|
if (!Component) {
|
||||||
// This only happens when we create a new page without a default export.
|
// This only happens when we create a new page without a default export.
|
||||||
// If you removed a default export from a exising viewing page, this has no effect.
|
// If you removed a default export from a exising viewing page, this has no effect.
|
||||||
console.log(`Hard reloading due to no default component in page: ${route}`)
|
console.log(`Hard reloading due to no default component in page: ${route}`)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
// reload to recover from runtime errors
|
|
||||||
Router.reload(route)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
dynamic.js
Normal file
1
dynamic.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require('./dist/lib/dynamic')
|
|
@ -11,6 +11,5 @@
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"name": "custom-server-express",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"name": "custom-server-hapi",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "custom-server-koa",
|
"name": "custom-server-koa",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"name": "custom-server",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -12,6 +12,5 @@
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "*",
|
"next": "latest",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "*",
|
"next": "latest",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "hello-world",
|
"name": "layout-component",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
|
@ -7,10 +7,9 @@
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "*",
|
"next": "latest",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "*",
|
"next": "latest",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
29
examples/page-transitions/README.md
Normal file
29
examples/page-transitions/README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/page-transitions)
|
||||||
|
|
||||||
|
# Example app with custom page transitions
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/page-transitions
|
||||||
|
cd page-transitions
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
Being able to animate out old content and animate in new content is a fairly standard thing to do these days. We can hijack the route change and do any animations that we want: sliding, cross fading, scaling, et al.
|
19
examples/page-transitions/package.json
Normal file
19
examples/page-transitions/package.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "page-transitions",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "NODE_ENV=development node server.js",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "NODE_ENV=production node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"compression": "^1.7.0",
|
||||||
|
"express": "^4.15.3",
|
||||||
|
"next": "latest",
|
||||||
|
"next-routes": "^1.0.40",
|
||||||
|
"raf": "^3.3.2",
|
||||||
|
"react": "^15.4.2",
|
||||||
|
"react-dom": "^15.4.2"
|
||||||
|
},
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
21
examples/page-transitions/pages/_document.js
Normal file
21
examples/page-transitions/pages/_document.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Document, { Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
// ---------------------------------------------
|
||||||
|
|
||||||
|
export default class CustomDocument extends Document {
|
||||||
|
render () {
|
||||||
|
return (<html lang='en-US'>
|
||||||
|
<Head>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
<link rel='stylesheet' href='/static/style.css' />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</html>)
|
||||||
|
}
|
||||||
|
}
|
39
examples/page-transitions/pages/index.js
Normal file
39
examples/page-transitions/pages/index.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { PureComponent } from 'react'
|
||||||
|
import raf from 'raf'
|
||||||
|
import { Router } from '../routes'
|
||||||
|
import Main from './main'
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
|
Router.onRouteChangeStart = () => {
|
||||||
|
const $container = document.getElementById('container')
|
||||||
|
const $clone = $container.cloneNode(true)
|
||||||
|
|
||||||
|
document.body.classList.add('loading')
|
||||||
|
$clone.classList.add('clone')
|
||||||
|
|
||||||
|
raf(() => {
|
||||||
|
$container.parentNode.insertBefore($clone, $container.nextSibling)
|
||||||
|
$clone.classList.add('animate-out')
|
||||||
|
$container.classList.add('animate-in')
|
||||||
|
})
|
||||||
|
|
||||||
|
$clone.addEventListener('animationend', () => {
|
||||||
|
document.body.classList.remove('loading')
|
||||||
|
$container.classList.remove('animate-in')
|
||||||
|
$clone.parentNode.removeChild($clone)
|
||||||
|
}, { once: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
|
export default class Index extends PureComponent {
|
||||||
|
static async getInitialProps ({ query }) {
|
||||||
|
const pathname = query.slug || '/'
|
||||||
|
return { pathname }
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return <Main {...this.props} />
|
||||||
|
}
|
||||||
|
}
|
38
examples/page-transitions/pages/main.js
Normal file
38
examples/page-transitions/pages/main.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { PureComponent } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { Link } from '../routes'
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
|
export default class Main extends PureComponent {
|
||||||
|
render () {
|
||||||
|
return (<div>
|
||||||
|
<Head>
|
||||||
|
<title>{this.props.pathname} - Page Transitions</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<Link to='/'>
|
||||||
|
<a className={this.props.pathname === '/' ? 'active' : ''}>Homepage</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link route='main' params={{ slug: 'about' }}>
|
||||||
|
<a className={this.props.pathname === 'about' ? 'active' : ''}>About</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link route='main' params={{ slug: 'contact' }}>
|
||||||
|
<a className={this.props.pathname === 'contact' ? 'active' : ''}>Contact</a>
|
||||||
|
</Link>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id='container' className={`page-${this.props.pathname}`}>
|
||||||
|
<h1 dangerouslySetInnerHTML={{ __html: this.props.pathname }} />
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Main.propTypes = {
|
||||||
|
pathname: PropTypes.string.isRequired
|
||||||
|
}
|
6
examples/page-transitions/routes.js
Normal file
6
examples/page-transitions/routes.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
const nextRoutes = require('next-routes')
|
||||||
|
const routes = nextRoutes()
|
||||||
|
|
||||||
|
routes.add('main', '/:slug/:child?', '')
|
||||||
|
|
||||||
|
module.exports = routes
|
12
examples/page-transitions/server.js
Normal file
12
examples/page-transitions/server.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const next = require('next')
|
||||||
|
const routes = require('./routes')
|
||||||
|
const express = require('express')
|
||||||
|
const compression = require('compression')
|
||||||
|
|
||||||
|
const port = process.env.PORT || 3000
|
||||||
|
const dev = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
|
const app = next({ dev })
|
||||||
|
const handle = routes.getRequestHandler(app)
|
||||||
|
|
||||||
|
app.prepare().then(express().use(compression()).use(handle).listen(port))
|
99
examples/page-transitions/static/style.css
Normal file
99
examples/page-transitions/static/style.css
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------- */
|
||||||
|
|
||||||
|
header {
|
||||||
|
z-index: 100;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 20px;
|
||||||
|
left: 0;
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a:not(:last-child) {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a.active {
|
||||||
|
color: #000;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------- */
|
||||||
|
|
||||||
|
@keyframes animateIn {
|
||||||
|
from {
|
||||||
|
transform: translate3d(100vw, 0, 0);;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animateOut {
|
||||||
|
to {
|
||||||
|
transform: translate3d(-100vw, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------- */
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
box-shadow: inset 0 0 0 20px;
|
||||||
|
background: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container.page-about {
|
||||||
|
background: tan;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container.page-contact {
|
||||||
|
background: violet;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container h1 {
|
||||||
|
font-size: 10vw;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container[class*="animate-"] {
|
||||||
|
position: fixed;
|
||||||
|
animation: animateIn .75s ease-in-out forwards;
|
||||||
|
transform-origin: center;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container.animate-out {
|
||||||
|
animation-name: animateOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading #container:not(.clone) h1 {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeIn 1s .15s ease-in-out forwards;
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"name": "parameterized-routing",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "progressive-render",
|
"name": "progressive-render",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"name": "root-static-files",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "shared-modules",
|
"name": "shared-modules",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "This example features:",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "*",
|
"next": "latest",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"name": "ssr-caching",
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|
|
@ -13,6 +13,5 @@
|
||||||
"module-alias": "^2.0.0",
|
"module-alias": "^2.0.0",
|
||||||
"next": "latest"
|
"next": "latest"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
webpack: function (config, { dev }) {
|
webpack: function (config, { dev }) {
|
||||||
// For the development version, we'll use React.
|
// For the development version, we'll use React.
|
||||||
// Because, it support react hot loading and so on.
|
// Because, it supports react hot loading and so on.
|
||||||
if (dev) {
|
if (dev) {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
"preact": "^7.2.0",
|
"preact": "^7.2.0",
|
||||||
"preact-compat": "^3.14.0"
|
"preact-compat": "^3.14.0"
|
||||||
},
|
},
|
||||||
"author": "",
|
"license": "ISC",
|
||||||
"license": "ISC"
|
"devDependencies": {
|
||||||
|
"react": "~15.6.1",
|
||||||
|
"react-dom": "~15.6.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "shared-modules",
|
"name": "using-router",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "This example features:",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
@ -13,6 +11,5 @@
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
19
examples/with-algolia-react-instantsearch/.gitignore
vendored
Normal file
19
examples/with-algolia-react-instantsearch/.gitignore
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
/.next
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
34
examples/with-algolia-react-instantsearch/README.md
Normal file
34
examples/with-algolia-react-instantsearch/README.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-algolia-react-instantsearch)
|
||||||
|
|
||||||
|
# With Algolia React InstantSearch example
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-algolia-react-instantsearch
|
||||||
|
cd with-algolia-react-instantsearch
|
||||||
|
```
|
||||||
|
|
||||||
|
Set up Algolia:
|
||||||
|
- create an [algolia](https://www.algolia.com/) account or use this already [configured index](https://community.algolia.com/react-instantsearch/Getting_started.html#before-we-start)
|
||||||
|
- update the appId, apikey and indexName you want to search on in components/app.js
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
The goal of this example is to illustrate how you can use [Algolia React InstantSearch](https://community.algolia.com/react-instantsearch/) to perform
|
||||||
|
your search with a Server-rendered application developed with Next.js. It also illustrates how you
|
||||||
|
can keep in sync the Url with the search.
|
86
examples/with-algolia-react-instantsearch/components/app.js
Normal file
86
examples/with-algolia-react-instantsearch/components/app.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import {
|
||||||
|
RefinementList,
|
||||||
|
SearchBox,
|
||||||
|
Hits,
|
||||||
|
Configure,
|
||||||
|
Highlight,
|
||||||
|
Pagination
|
||||||
|
} from 'react-instantsearch/dom'
|
||||||
|
import { InstantSearch } from './instantsearch'
|
||||||
|
|
||||||
|
const HitComponent = ({ hit }) =>
|
||||||
|
<div className='hit'>
|
||||||
|
<div>
|
||||||
|
<div className='hit-picture'>
|
||||||
|
<img src={`${hit.image}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='hit-content'>
|
||||||
|
<div>
|
||||||
|
<Highlight attributeName='name' hit={hit} />
|
||||||
|
<span>
|
||||||
|
{' '}- ${hit.price}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{' '}- {hit.rating} stars
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='hit-type'>
|
||||||
|
<Highlight attributeName='type' hit={hit} />
|
||||||
|
</div>
|
||||||
|
<div className='hit-description'>
|
||||||
|
<Highlight attributeName='description' hit={hit} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
HitComponent.propTypes = {
|
||||||
|
hit: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
searchState: PropTypes.object,
|
||||||
|
resultsState: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||||
|
onSearchStateChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<InstantSearch
|
||||||
|
appId='appId' // change this
|
||||||
|
apiKey='apiKey' // change this
|
||||||
|
indexName='indexName' // change this
|
||||||
|
resultsState={this.props.resultsState}
|
||||||
|
onSearchStateChange={this.props.onSearchStateChange}
|
||||||
|
searchState={this.props.searchState}
|
||||||
|
>
|
||||||
|
<Configure hitsPerPage={10} />
|
||||||
|
<header>
|
||||||
|
<h1>React InstantSearch + Next.Js</h1>
|
||||||
|
<SearchBox />
|
||||||
|
</header>
|
||||||
|
<content>
|
||||||
|
<menu>
|
||||||
|
<RefinementList attributeName='category' />
|
||||||
|
</menu>
|
||||||
|
<results>
|
||||||
|
<Hits hitComponent={HitComponent} />
|
||||||
|
</results>
|
||||||
|
</content>
|
||||||
|
<footer>
|
||||||
|
<Pagination />
|
||||||
|
<div>
|
||||||
|
See{' '}
|
||||||
|
<a href='https://github.com/algolia/react-instantsearch/tree/master/packages/react-instantsearch/examples/next-app'>
|
||||||
|
source code
|
||||||
|
</a>{' '}
|
||||||
|
on github
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</InstantSearch>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
48
examples/with-algolia-react-instantsearch/components/head.js
Normal file
48
examples/with-algolia-react-instantsearch/components/head.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import NextHead from 'next/head'
|
||||||
|
import { string } from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const defaultDescription = ''
|
||||||
|
const defaultOGURL = ''
|
||||||
|
const defaultOGImage = ''
|
||||||
|
|
||||||
|
export const Head = props =>
|
||||||
|
<NextHead>
|
||||||
|
<meta charSet='UTF-8' />
|
||||||
|
<title>{props.title || ''}</title>
|
||||||
|
<meta
|
||||||
|
name='description'
|
||||||
|
content={props.description || defaultDescription}
|
||||||
|
/>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||||
|
<link rel='icon' sizes='192x192' href='/static/touch-icon.png' />
|
||||||
|
<link rel='apple-touch-icon' href='/static/touch-icon.png' />
|
||||||
|
<link rel='mask-icon' href='/static/favicon-mask.svg' color='#49B882' />
|
||||||
|
<link rel='icon' href='/static/favicon.ico' />
|
||||||
|
<meta property='og:url' content={props.url || defaultOGURL} />
|
||||||
|
<meta property='og:title' content={props.title || ''} />
|
||||||
|
<meta
|
||||||
|
property='og:description'
|
||||||
|
content={props.description || defaultDescription}
|
||||||
|
/>
|
||||||
|
<meta name='twitter:site' content={props.url || defaultOGURL} />
|
||||||
|
<meta name='twitter:card' content='summary_large_image' />
|
||||||
|
<meta name='twitter:image' content={props.ogImage || defaultOGImage} />
|
||||||
|
<meta property='og:image' content={props.ogImage || defaultOGImage} />
|
||||||
|
<meta property='og:image:width' content='1200' />
|
||||||
|
<meta property='og:image:height' content='630' />
|
||||||
|
<link
|
||||||
|
rel='stylesheet'
|
||||||
|
href='https://unpkg.com/react-instantsearch-theme-algolia@3.0.0/style.min.css'
|
||||||
|
/>
|
||||||
|
<link rel='stylesheet' href='../static/instantsearch.css' />
|
||||||
|
</NextHead>
|
||||||
|
|
||||||
|
Head.propTypes = {
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
url: string,
|
||||||
|
ogImage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Head
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './head'
|
||||||
|
export { default as App } from './app'
|
||||||
|
export { findResultsState } from './instantsearch'
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { createInstantSearch } from 'react-instantsearch/server'
|
||||||
|
|
||||||
|
const { InstantSearch, findResultsState } = createInstantSearch()
|
||||||
|
|
||||||
|
export { InstantSearch, findResultsState }
|
19
examples/with-algolia-react-instantsearch/next.config.js
Normal file
19
examples/with-algolia-react-instantsearch/next.config.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/* eslint-disable import/no-commonjs */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
webpack: config => {
|
||||||
|
// Fixes npm packages that depend on `fs` module
|
||||||
|
config.node = {
|
||||||
|
fs: 'empty'
|
||||||
|
}
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.css$/,
|
||||||
|
loader: ['style-loader', 'css-loader']
|
||||||
|
})
|
||||||
|
if (config.resolve.alias) {
|
||||||
|
delete config.resolve.alias.react
|
||||||
|
delete config.resolve.alias['react-dom']
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
19
examples/with-algolia-react-instantsearch/package.json
Normal file
19
examples/with-algolia-react-instantsearch/package.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "create-next-example-app",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "NODE_ENV=development next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"css-loader": "^0.28.1",
|
||||||
|
"next": "^2.4.7",
|
||||||
|
"prop-types": "^15.5.10",
|
||||||
|
"qs": "^6.4.0",
|
||||||
|
"react": "^15.5.4",
|
||||||
|
"react-dom": "^15.5.4",
|
||||||
|
"react-instantsearch": "beta",
|
||||||
|
"react-instantsearch-theme-algolia": "beta",
|
||||||
|
"style-loader": "^0.17.0"
|
||||||
|
}
|
||||||
|
}
|
73
examples/with-algolia-react-instantsearch/pages/index.js
Normal file
73
examples/with-algolia-react-instantsearch/pages/index.js
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { Head, App, findResultsState } from '../components'
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Router from 'next/router'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
const updateAfter = 700
|
||||||
|
|
||||||
|
const searchStateToUrl = searchState =>
|
||||||
|
searchState ? `${window.location.pathname}?${qs.stringify(searchState)}` : ''
|
||||||
|
|
||||||
|
export default class extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
resultsState: PropTypes.object,
|
||||||
|
searchState: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.onSearchStateChange = this.onSearchStateChange.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
nextjs params.query doesn't handle nested objects
|
||||||
|
once it does, params.query could be used directly here, but also inside the constructor
|
||||||
|
to initialize the searchState.
|
||||||
|
*/
|
||||||
|
static async getInitialProps (params) {
|
||||||
|
const searchState = qs.parse(
|
||||||
|
params.asPath.substring(params.asPath.indexOf('?') + 1)
|
||||||
|
)
|
||||||
|
const resultsState = await findResultsState(App, { searchState })
|
||||||
|
return { resultsState, searchState }
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchStateChange = searchState => {
|
||||||
|
clearTimeout(this.debouncedSetState)
|
||||||
|
this.debouncedSetState = setTimeout(() => {
|
||||||
|
const href = searchStateToUrl(searchState)
|
||||||
|
Router.push(href, href, {
|
||||||
|
shallow: true
|
||||||
|
})
|
||||||
|
}, updateAfter)
|
||||||
|
this.setState({ searchState })
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.setState({ searchState: qs.parse(window.location.search.slice(1)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps () {
|
||||||
|
this.setState({ searchState: qs.parse(window.location.search.slice(1)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Head title='Home' />
|
||||||
|
<div>
|
||||||
|
<App
|
||||||
|
resultsState={this.props.resultsState}
|
||||||
|
onSearchStateChange={this.onSearchStateChange}
|
||||||
|
searchState={
|
||||||
|
this.state && this.state.searchState
|
||||||
|
? this.state.searchState
|
||||||
|
: this.props.searchState
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
BIN
examples/with-algolia-react-instantsearch/static/favicon.ico
Normal file
BIN
examples/with-algolia-react-instantsearch/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,48 @@
|
||||||
|
.ais-InstantSearch__root {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
content {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
results {
|
||||||
|
flex: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hit-actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hit-content {
|
||||||
|
padding: 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hit-picture img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hit-type {
|
||||||
|
color: #888888;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "amp",
|
"name": "with-amp",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
|
@ -11,6 +11,5 @@
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2"
|
"react-dom": "^15.4.2"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,5 @@
|
||||||
"react": "^15.5.4",
|
"react": "^15.5.4",
|
||||||
"react-dom": "^15.5.4"
|
"react-dom": "^15.5.4"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-material-ui)
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-antd-mobile)
|
||||||
# Ant Design Mobile example
|
# Ant Design Mobile example
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -36,9 +36,15 @@ const tabBarData = [
|
||||||
link: '/home'
|
link: '/home'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Trick',
|
title: 'Icon',
|
||||||
icon: 'check-circle-o',
|
icon: 'check-circle-o',
|
||||||
selectedIcon: 'check-circle',
|
selectedIcon: 'check-circle',
|
||||||
|
link: '/icon'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Trick',
|
||||||
|
icon: 'cross-circle-o',
|
||||||
|
selectedIcon: 'cross-circle',
|
||||||
link: '/trick'
|
link: '/trick'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,29 +3,20 @@ const fs = require('fs')
|
||||||
const requireHacker = require('require-hacker')
|
const requireHacker = require('require-hacker')
|
||||||
|
|
||||||
function setupRequireHacker () {
|
function setupRequireHacker () {
|
||||||
requireHacker.resolver((filename, module) => {
|
const webjs = '.web.js'
|
||||||
if (filename.endsWith('/style/css')) {
|
const webModules = ['antd-mobile', 'rmc-picker'].map(m => path.join('node_modules', m))
|
||||||
return requireHacker.resolve(`${filename}.web.js`, module)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
requireHacker.hook('js', filename => {
|
requireHacker.hook('js', filename => {
|
||||||
if (
|
if (filename.endsWith(webjs) || webModules.every(p => !filename.includes(p))) return
|
||||||
filename.endsWith('.web.js') ||
|
|
||||||
!filename.includes('/node_modules/') ||
|
|
||||||
['antd-mobile', 'rc-swipeout', 'rmc-picker'].every(p => !filename.includes(p))
|
|
||||||
) return
|
|
||||||
|
|
||||||
const webjs = filename.replace(/\.js$/, '.web.js')
|
const webFilename = filename.replace(/\.js$/, webjs)
|
||||||
if (!fs.existsSync(webjs)) return
|
if (!fs.existsSync(webFilename)) return
|
||||||
|
|
||||||
return fs.readFileSync(webjs, { encoding: 'utf8' })
|
return fs.readFileSync(webFilename, { encoding: 'utf8' })
|
||||||
})
|
})
|
||||||
|
|
||||||
requireHacker.hook('css', () => '')
|
|
||||||
|
|
||||||
requireHacker.hook('svg', filename => {
|
requireHacker.hook('svg', filename => {
|
||||||
return requireHacker.to_javascript_module_source(fs.readFileSync(filename, { encoding: 'utf8' }))
|
return requireHacker.to_javascript_module_source(`#${path.parse(filename).name}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +31,23 @@ module.exports = {
|
||||||
config.resolve.extensions = ['.web.js', '.js', '.json']
|
config.resolve.extensions = ['.web.js', '.js', '.json']
|
||||||
|
|
||||||
config.module.rules.push(
|
config.module.rules.push(
|
||||||
|
{
|
||||||
|
test: /\.(svg)$/i,
|
||||||
|
loader: 'emit-file-loader',
|
||||||
|
options: {
|
||||||
|
name: 'dist/[path][name].[ext]'
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
moduleDir('antd-mobile'),
|
||||||
|
__dirname
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.(svg)$/i,
|
test: /\.(svg)$/i,
|
||||||
loader: 'svg-sprite-loader',
|
loader: 'svg-sprite-loader',
|
||||||
include: [
|
include: [
|
||||||
moduleDir('antd-mobile')
|
moduleDir('antd-mobile'),
|
||||||
|
__dirname
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
{
|
{
|
||||||
|
"name": "with-antd-mobile",
|
||||||
|
"version": "1.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd-mobile": "^1.1.2",
|
"antd-mobile": "1.4.0",
|
||||||
"babel-plugin-import": "^1.1.1",
|
"babel-plugin-import": "^1.2.1",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"react": "^15.5.4",
|
"react": "^15.6.1",
|
||||||
"react-dom": "^15.5.4",
|
"react-dom": "^15.6.1",
|
||||||
"require-hacker": "^3.0.0",
|
"require-hacker": "^3.0.0",
|
||||||
"svg-sprite-loader": "~0.3.0"
|
"svg-sprite-loader": "0.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
|
|
81
examples/with-antd-mobile/pages/icon.js
Normal file
81
examples/with-antd-mobile/pages/icon.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import { WhiteSpace, WingBlank, Card, Icon } from 'antd-mobile'
|
||||||
|
import Layout from '../components/Layout'
|
||||||
|
import MenuBar from '../components/MenuBar'
|
||||||
|
|
||||||
|
export default class Home extends Component {
|
||||||
|
static getInitialProps ({ req }) {
|
||||||
|
const language = req ? req.headers['accept-language'] : navigator.language
|
||||||
|
|
||||||
|
return {
|
||||||
|
language
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
language,
|
||||||
|
url: { pathname }
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout language={language}>
|
||||||
|
<MenuBar
|
||||||
|
pathname={pathname}
|
||||||
|
>
|
||||||
|
<WingBlank>
|
||||||
|
<WhiteSpace />
|
||||||
|
<Card>
|
||||||
|
<Card.Header
|
||||||
|
extra='Internal svg'
|
||||||
|
thumb={<Icon type='check' />}
|
||||||
|
/>
|
||||||
|
<Card.Body>
|
||||||
|
<code>
|
||||||
|
{`<Icon type='check' />`}
|
||||||
|
</code>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
<WhiteSpace />
|
||||||
|
<Card>
|
||||||
|
<Card.Header
|
||||||
|
extra='Custom svg'
|
||||||
|
thumb={<Icon type={require('../static/reload.svg')} />}
|
||||||
|
/>
|
||||||
|
<Card.Body>
|
||||||
|
<code>
|
||||||
|
{`<Icon type={require('../static/reload.svg')} />`}
|
||||||
|
</code>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
<WhiteSpace />
|
||||||
|
<Card>
|
||||||
|
<Card.Header
|
||||||
|
extra='Fill color'
|
||||||
|
thumb={
|
||||||
|
<Icon
|
||||||
|
type={require('../static/reload.svg')}
|
||||||
|
style={{ fill: '#108ee9' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Card.Body>
|
||||||
|
<code>{`
|
||||||
|
<Icon
|
||||||
|
type={require('../static/reload.svg')}
|
||||||
|
style={{ fill: '#108ee9' }}
|
||||||
|
/>
|
||||||
|
`}</code>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
<style jsx>{`
|
||||||
|
code {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</WingBlank>
|
||||||
|
</MenuBar>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,5 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import {
|
import { WhiteSpace, List, Switch, Menu } from 'antd-mobile'
|
||||||
WhiteSpace,
|
|
||||||
List, Switch, Modal, Button, Menu
|
|
||||||
} from 'antd-mobile'
|
|
||||||
import Layout from '../components/Layout'
|
import Layout from '../components/Layout'
|
||||||
import MenuBar from '../components/MenuBar'
|
import MenuBar from '../components/MenuBar'
|
||||||
|
|
||||||
|
@ -19,9 +16,7 @@ export default class Trick extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
componentWillMount () {
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.menuData = [
|
this.menuData = [
|
||||||
{
|
{
|
||||||
label: 'Menu 1',
|
label: 'Menu 1',
|
||||||
|
@ -58,8 +53,7 @@ export default class Trick extends Component {
|
||||||
]
|
]
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
switchChecked: true,
|
switchChecked: true
|
||||||
modalOpened: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +65,7 @@ export default class Trick extends Component {
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const {
|
const {
|
||||||
switchChecked,
|
switchChecked
|
||||||
modalOpened
|
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -81,7 +74,7 @@ export default class Trick extends Component {
|
||||||
pathname={pathname}
|
pathname={pathname}
|
||||||
>
|
>
|
||||||
<WhiteSpace />
|
<WhiteSpace />
|
||||||
<List renderHeader={() => 'Switch and Modal platform prop is required in SSR mode'}>
|
<List renderHeader={() => 'Switch platform prop is required in SSR mode'}>
|
||||||
<List.Item
|
<List.Item
|
||||||
extra={
|
extra={
|
||||||
<Switch
|
<Switch
|
||||||
|
@ -93,22 +86,7 @@ export default class Trick extends Component {
|
||||||
>
|
>
|
||||||
Switch {platform}
|
Switch {platform}
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<Button onClick={() => this.setState({ modalOpened: true })}>
|
|
||||||
Open {platform} modal
|
|
||||||
</Button>
|
|
||||||
</List>
|
</List>
|
||||||
<Modal
|
|
||||||
title='Modal'
|
|
||||||
platform={platform}
|
|
||||||
visible={modalOpened}
|
|
||||||
transparent
|
|
||||||
closable
|
|
||||||
footer={[{ text: 'OK', onPress: () => this.setState({ modalOpened: false }) }]}
|
|
||||||
onClose={() => this.setState({ modalOpened: false })}
|
|
||||||
>
|
|
||||||
I am a modal.<br />
|
|
||||||
Must set platform prop
|
|
||||||
</Modal>
|
|
||||||
<List renderHeader={() => 'Menu height prop is required in SSR mode'}>
|
<List renderHeader={() => 'Menu height prop is required in SSR mode'}>
|
||||||
<Menu
|
<Menu
|
||||||
height={500}
|
height={500}
|
||||||
|
|
1
examples/with-antd-mobile/static/reload.svg
Normal file
1
examples/with-antd-mobile/static/reload.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><title>Share Icons Copy</title><path d="M59.177 29.5s-1.25 0-1.25 2.5c0 14.471-11.786 26.244-26.253 26.244C17.206 58.244 5.417 46.47 5.417 32c0-13.837 11.414-25.29 25.005-26.26v6.252c0 .622-.318 1.635.198 1.985a1.88 1.88 0 0 0 1.75.19l21.37-8.545c.837-.334 1.687-1.133 1.687-2.384C55.425 1.99 53.944 2 53.044 2h-21.37C15.134 2 1.667 15.46 1.667 32c0 16.543 13.467 30 30.007 30 16.538 0 29.918-13.458 29.993-30 .01-2.5-1.24-2.5-1.24-2.5h-1.25" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 553 B |
|
@ -33,6 +33,7 @@ const upvotePost = gql`
|
||||||
mutation updatePost($id: ID!, $votes: Int) {
|
mutation updatePost($id: ID!, $votes: Int) {
|
||||||
updatePost(id: $id, votes: $votes) {
|
updatePost(id: $id, votes: $votes) {
|
||||||
id
|
id
|
||||||
|
__typename
|
||||||
votes
|
votes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +44,9 @@ export default graphql(upvotePost, {
|
||||||
upvote: (id, votes) => mutate({
|
upvote: (id, votes) => mutate({
|
||||||
variables: { id, votes },
|
variables: { id, votes },
|
||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
|
__typename: 'Mutation',
|
||||||
updatePost: {
|
updatePost: {
|
||||||
|
__typename: 'Post',
|
||||||
id: ownProps.id,
|
id: ownProps.id,
|
||||||
votes: ownProps.votes + 1
|
votes: ownProps.votes + 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { ApolloProvider, getDataFromTree } from 'react-apollo'
|
import { ApolloProvider, getDataFromTree } from 'react-apollo'
|
||||||
|
import Head from 'next/head'
|
||||||
import initApollo from './initApollo'
|
import initApollo from './initApollo'
|
||||||
import initRedux from './initRedux'
|
import initRedux from './initRedux'
|
||||||
|
|
||||||
|
@ -47,6 +48,9 @@ export default ComposedComponent => {
|
||||||
// Handle them in components via the data.error prop:
|
// Handle them in components via the data.error prop:
|
||||||
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
|
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
|
||||||
}
|
}
|
||||||
|
// getDataFromTree does not call componentWillUnmount
|
||||||
|
// head side effect therefore need to be cleared manually
|
||||||
|
Head.rewind()
|
||||||
|
|
||||||
// Extract query data from the store
|
// Extract query data from the store
|
||||||
const state = redux.getState()
|
const state = redux.getState()
|
||||||
|
|
16
examples/with-apollo-auth/.babelrc
Normal file
16
examples/with-apollo-auth/.babelrc
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"development": {
|
||||||
|
"presets": "next/babel"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"presets": "next/babel"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"presets": [
|
||||||
|
["env", { "modules": "commonjs" }],
|
||||||
|
"next/babel"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
examples/with-apollo-auth/.gitignore
vendored
Normal file
80
examples/with-apollo-auth/.gitignore
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/vim,node
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Typescript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
|
||||||
|
### Vim ###
|
||||||
|
# swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-v][a-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
# session
|
||||||
|
Session.vim
|
||||||
|
# temporary
|
||||||
|
.netrwhist
|
||||||
|
*~
|
||||||
|
# auto-generated tag files
|
||||||
|
tags
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/vim,node
|
56
examples/with-apollo-auth/README.md
Normal file
56
examples/with-apollo-auth/README.md
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-apollo-auth)
|
||||||
|
# Apollo With Authentication Example
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
https://next-with-apollo-auth.now.sh
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://codeload.github.com/zeit/next.js/tar.gz/v3-beta | tar -xz --strip=2 next.js-3-beta/examples/with-apollo-auth
|
||||||
|
cd with-apollo-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This is an extention of the _[with Apollo](https://github.com/zeit/next.js/tree/master/examples/with-apollo#the-idea-behind-the-example)_ example:
|
||||||
|
|
||||||
|
> [Apollo](http://dev.apollodata.com) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server.
|
||||||
|
>
|
||||||
|
> In this simple example, we integrate Apollo seamlessly with Next by wrapping our *pages* inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). Using the HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application.
|
||||||
|
>
|
||||||
|
> On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
|
||||||
|
>
|
||||||
|
> This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
|
||||||
|
>
|
||||||
|
> *Note: Apollo uses Redux internally; if you're interested in integrating the client with your existing Redux store check out the [`with-apollo-and-redux`](https://github.com/zeit/next.js/tree/master/examples/with-apollo-and-redux) example.*
|
||||||
|
|
||||||
|
[graph.cool](https://www.graph.cool) can be setup with many different
|
||||||
|
[authentication providers](https://www.graph.cool/docs/reference/integrations/overview-seimeish6e/#authentication-providers), the most basic of which is [email-password authentication](https://www.graph.cool/docs/reference/simple-api/user-authentication-eixu9osueb/#email-and-password). Once email-password authentication is enabled for your graph.cool project, you are provided with 2 useful mutations: `createUser` and `signinUser`.
|
||||||
|
|
||||||
|
On loading each route, we perform a `user` query to see if the current visitor is logged in (based on a cookie, more on that in a moment). Depending on the query result, and the route, the user may be [redirected](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/lib/redirect.js) to a different page.
|
||||||
|
|
||||||
|
When creating an account, both the `createUser` and `signinUser` mutations are executed on graph.cool, which returns a token that can be used to [authenticate the user for future requests](https://www.graph.cool/docs/reference/auth/authentication-tokens-eip7ahqu5o/). The token is stored in a cookie for easy access (_note: This may have security implications. Please understand XSS and JWT before deploying this to production_).
|
||||||
|
|
||||||
|
A similar process is followed when signing in, except `signinUser` is the only mutation executed.
|
||||||
|
|
||||||
|
It is important to note the use of Apollo's `resetStore()` method after signing in and signing out to ensure that no user data is kept in the browser's memory.
|
||||||
|
|
||||||
|
To get this example running locally, you will need to create a graph.cool
|
||||||
|
account, and provide [the `project.graphcool` schema](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/project.graphcool).
|
19
examples/with-apollo-auth/lib/check-logged-in.js
Normal file
19
examples/with-apollo-auth/lib/check-logged-in.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export default (context, apolloClient) => (
|
||||||
|
apolloClient.query({
|
||||||
|
query: gql`
|
||||||
|
query getUser {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}).then(({ data }) => {
|
||||||
|
return { loggedInUser: data }
|
||||||
|
}).catch(() => {
|
||||||
|
// Fail gracefully
|
||||||
|
return { loggedInUser: {} }
|
||||||
|
})
|
||||||
|
)
|
47
examples/with-apollo-auth/lib/init-apollo.js
Normal file
47
examples/with-apollo-auth/lib/init-apollo.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { ApolloClient, createNetworkInterface } from 'react-apollo'
|
||||||
|
import fetch from 'isomorphic-fetch'
|
||||||
|
|
||||||
|
let apolloClient = null
|
||||||
|
|
||||||
|
// Polyfill fetch() on the server (used by apollo-client)
|
||||||
|
if (!process.browser) {
|
||||||
|
global.fetch = fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
function create (initialState, { getToken }) {
|
||||||
|
const networkInterface = createNetworkInterface({
|
||||||
|
uri: 'https://api.graph.cool/simple/v1/cj3h80ffbllm20162alevpcby'
|
||||||
|
})
|
||||||
|
|
||||||
|
networkInterface.use([{
|
||||||
|
applyMiddleware (req, next) {
|
||||||
|
if (!req.options.headers) {
|
||||||
|
req.options.headers = {} // Create the header object if needed.
|
||||||
|
}
|
||||||
|
const token = getToken()
|
||||||
|
req.options.headers.authorization = token ? `Bearer ${token}` : null
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
|
||||||
|
return new ApolloClient({
|
||||||
|
initialState,
|
||||||
|
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
|
||||||
|
networkInterface
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function initApollo (initialState, options) {
|
||||||
|
// Make sure to create a new client for every server-side request so that data
|
||||||
|
// isn't shared between connections (which would be bad)
|
||||||
|
if (!process.browser) {
|
||||||
|
return create(initialState, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse client on the client-side
|
||||||
|
if (!apolloClient) {
|
||||||
|
apolloClient = create(initialState, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return apolloClient
|
||||||
|
}
|
13
examples/with-apollo-auth/lib/redirect.js
Normal file
13
examples/with-apollo-auth/lib/redirect.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import Router from 'next/router'
|
||||||
|
|
||||||
|
export default (context, target) => {
|
||||||
|
if (context.res) {
|
||||||
|
// server
|
||||||
|
// 303: "See other"
|
||||||
|
context.res.writeHead(303, { Location: target })
|
||||||
|
context.res.end()
|
||||||
|
} else {
|
||||||
|
// In the browser, we just pretend like this never even happened ;)
|
||||||
|
Router.replace(target)
|
||||||
|
}
|
||||||
|
}
|
94
examples/with-apollo-auth/lib/with-data.js
Normal file
94
examples/with-apollo-auth/lib/with-data.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import React from 'react'
|
||||||
|
import cookie from 'cookie'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { ApolloProvider, getDataFromTree } from 'react-apollo'
|
||||||
|
|
||||||
|
import initApollo from './init-apollo'
|
||||||
|
|
||||||
|
function parseCookies (ctx = {}, options = {}) {
|
||||||
|
return cookie.parse(
|
||||||
|
ctx.req && ctx.req.headers.cookie
|
||||||
|
? ctx.req.headers.cookie
|
||||||
|
: document.cookie,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ComposedComponent => {
|
||||||
|
return class WithData extends React.Component {
|
||||||
|
static displayName = `WithData(${ComposedComponent.displayName})`
|
||||||
|
static propTypes = {
|
||||||
|
serverState: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getInitialProps (context) {
|
||||||
|
let serverState = {}
|
||||||
|
|
||||||
|
// Setup a server-side one-time-use apollo client for initial props and
|
||||||
|
// rendering (on server)
|
||||||
|
let apollo = initApollo({}, {
|
||||||
|
getToken: () => parseCookies(context).token
|
||||||
|
})
|
||||||
|
|
||||||
|
// Evaluate the composed component's getInitialProps()
|
||||||
|
let composedInitialProps = {}
|
||||||
|
if (ComposedComponent.getInitialProps) {
|
||||||
|
composedInitialProps = await ComposedComponent.getInitialProps(context, apollo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all graphql queries in the component tree
|
||||||
|
// and extract the resulting data
|
||||||
|
if (!process.browser) {
|
||||||
|
if (context.res && context.res.finished) {
|
||||||
|
// When redirecting, the response is finished.
|
||||||
|
// No point in continuing to render
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the `url` prop data in case a graphql query uses it
|
||||||
|
const url = {query: context.query, pathname: context.pathname}
|
||||||
|
|
||||||
|
// Run all graphql queries
|
||||||
|
const app = (
|
||||||
|
<ApolloProvider client={apollo}>
|
||||||
|
<ComposedComponent url={url} {...composedInitialProps} />
|
||||||
|
</ApolloProvider>
|
||||||
|
)
|
||||||
|
await getDataFromTree(app)
|
||||||
|
|
||||||
|
// Extract query data from the Apollo's store
|
||||||
|
const state = apollo.getInitialState()
|
||||||
|
|
||||||
|
serverState = {
|
||||||
|
apollo: { // Make sure to only include Apollo's data state
|
||||||
|
data: state.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
serverState,
|
||||||
|
...composedInitialProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
// Note: Apollo should never be used on the server side beyond the initial
|
||||||
|
// render within `getInitialProps()` above (since the entire prop tree
|
||||||
|
// will be initialized there), meaning the below will only ever be
|
||||||
|
// executed on the client.
|
||||||
|
this.apollo = initApollo(this.props.serverState, {
|
||||||
|
getToken: () => parseCookies().token
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<ApolloProvider client={this.apollo}>
|
||||||
|
<ComposedComponent {...this.props} />
|
||||||
|
</ApolloProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
examples/with-apollo-auth/package.json
Normal file
33
examples/with-apollo-auth/package.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "with-apollo-auth",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"test": "NODE_ENV=test ava"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^0.3.1",
|
||||||
|
"graphql": "^0.9.3",
|
||||||
|
"isomorphic-fetch": "^2.2.1",
|
||||||
|
"next": "latest",
|
||||||
|
"prop-types": "^15.5.10",
|
||||||
|
"react": "^15.5.4",
|
||||||
|
"react-apollo": "^1.1.3",
|
||||||
|
"react-dom": "^15.5.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"ava": "^0.19.1",
|
||||||
|
"clear-require": "^2.0.0",
|
||||||
|
"glob": "^7.1.2"
|
||||||
|
},
|
||||||
|
"ava": {
|
||||||
|
"require": [
|
||||||
|
"babel-register"
|
||||||
|
],
|
||||||
|
"babel": "inherit"
|
||||||
|
}
|
||||||
|
}
|
104
examples/with-apollo-auth/pages/create-account.js
Normal file
104
examples/with-apollo-auth/pages/create-account.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { graphql, withApollo, compose } from 'react-apollo'
|
||||||
|
import cookie from 'cookie'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
import withData from '../lib/with-data'
|
||||||
|
import redirect from '../lib/redirect'
|
||||||
|
import checkLoggedIn from '../lib/check-logged-in'
|
||||||
|
|
||||||
|
class CreateAccount extends React.Component {
|
||||||
|
static async getInitialProps (context, apolloClient) {
|
||||||
|
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
|
||||||
|
|
||||||
|
if (loggedInUser.user) {
|
||||||
|
// Already signed in? No need to continue.
|
||||||
|
// Throw them back to the main page
|
||||||
|
redirect(context, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* this.props.create is the mutation function provided by apollo below */}
|
||||||
|
<form onSubmit={this.props.create}>
|
||||||
|
<input type='text' placeholder='Your Name' name='name' /><br />
|
||||||
|
<input type='email' placeholder='Email' name='email' /><br />
|
||||||
|
<input type='password' placeholder='Password' name='password' /><br />
|
||||||
|
<button>Create account</button>
|
||||||
|
</form>
|
||||||
|
<hr />
|
||||||
|
Already have an account? <Link prefetch href='/signin'><a>Sign in</a></Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
// withData gives us server-side graphql queries before rendering
|
||||||
|
withData,
|
||||||
|
// withApollo exposes `this.props.client` used when logging out
|
||||||
|
withApollo,
|
||||||
|
graphql(
|
||||||
|
// The `createUser` & `signinUser` mutations are provided by graph.cool by
|
||||||
|
// default.
|
||||||
|
// Multiple mutations are executed by graphql sequentially
|
||||||
|
gql`
|
||||||
|
mutation Create($name: String!, $email: String!, $password: String!) {
|
||||||
|
createUser(name: $name, authProvider: { email: { email: $email, password: $password }}) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
signinUser(email: { email: $email, password: $password }) {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
// Use an unambiguous name for use in the `props` section below
|
||||||
|
name: 'createWithEmail',
|
||||||
|
// Apollo's way of injecting new props which are passed to the component
|
||||||
|
props: ({
|
||||||
|
createWithEmail,
|
||||||
|
// `client` is provided by the `withApollo` HOC
|
||||||
|
ownProps: { client }
|
||||||
|
}) => ({
|
||||||
|
// `create` is the name of the prop passed to the component
|
||||||
|
create: (event) => {
|
||||||
|
/* global FormData */
|
||||||
|
const data = new FormData(event.target)
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
createWithEmail({
|
||||||
|
variables: {
|
||||||
|
email: data.get('email'),
|
||||||
|
password: data.get('password'),
|
||||||
|
name: data.get('name')
|
||||||
|
}
|
||||||
|
}).then(({ data: { signinUser: { token } } }) => {
|
||||||
|
// Store the token in cookie
|
||||||
|
document.cookie = cookie.serialize('token', token, {
|
||||||
|
maxAge: 30 * 24 * 60 * 60 // 30 days
|
||||||
|
})
|
||||||
|
|
||||||
|
// Force a reload of all the current queries now that the user is
|
||||||
|
// logged in
|
||||||
|
client.resetStore().then(() => {
|
||||||
|
// Now redirect to the homepage
|
||||||
|
redirect({}, '/')
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
// Something went wrong, such as incorrect password, or no network
|
||||||
|
// available, etc.
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)(CreateAccount)
|
49
examples/with-apollo-auth/pages/index.js
Normal file
49
examples/with-apollo-auth/pages/index.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import React from 'react'
|
||||||
|
import cookie from 'cookie'
|
||||||
|
import { withApollo, compose } from 'react-apollo'
|
||||||
|
|
||||||
|
import withData from '../lib/with-data'
|
||||||
|
import redirect from '../lib/redirect'
|
||||||
|
import checkLoggedIn from '../lib/check-logged-in'
|
||||||
|
|
||||||
|
class Index extends React.Component {
|
||||||
|
static async getInitialProps (context, apolloClient) {
|
||||||
|
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
|
||||||
|
|
||||||
|
if (!loggedInUser.user) {
|
||||||
|
// If not signed in, send them somewhere more useful
|
||||||
|
redirect(context, '/signin')
|
||||||
|
}
|
||||||
|
|
||||||
|
return { loggedInUser }
|
||||||
|
}
|
||||||
|
|
||||||
|
signout = () => {
|
||||||
|
document.cookie = cookie.serialize('token', '', {
|
||||||
|
maxAge: -1 // Expire the cookie immediately
|
||||||
|
})
|
||||||
|
|
||||||
|
// Force a reload of all the current queries now that the user is
|
||||||
|
// logged in, so we don't accidentally leave any state around.
|
||||||
|
this.props.client.resetStore().then(() => {
|
||||||
|
// Redirect to a more useful page when signed out
|
||||||
|
redirect({}, '/signin')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Hello {this.props.loggedInUser.user.name}!<br />
|
||||||
|
<button onClick={this.signout}>Sign out</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
// withData gives us server-side graphql queries before rendering
|
||||||
|
withData,
|
||||||
|
// withApollo exposes `this.props.client` used when logging out
|
||||||
|
withApollo
|
||||||
|
)(Index)
|
97
examples/with-apollo-auth/pages/signin.js
Normal file
97
examples/with-apollo-auth/pages/signin.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { graphql, withApollo, compose } from 'react-apollo'
|
||||||
|
import cookie from 'cookie'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
import withData from '../lib/with-data'
|
||||||
|
import redirect from '../lib/redirect'
|
||||||
|
import checkLoggedIn from '../lib/check-logged-in'
|
||||||
|
|
||||||
|
class Signin extends React.Component {
|
||||||
|
static async getInitialProps (context, apolloClient) {
|
||||||
|
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
|
||||||
|
|
||||||
|
if (loggedInUser.user) {
|
||||||
|
// Already signed in? No need to continue.
|
||||||
|
// Throw them back to the main page
|
||||||
|
redirect(context, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* this.props.signin is the mutation function provided by apollo below */}
|
||||||
|
<form onSubmit={this.props.signin}>
|
||||||
|
<input type='email' placeholder='Email' name='email' /><br />
|
||||||
|
<input type='password' placeholder='Password' name='password' /><br />
|
||||||
|
<button>Sign in</button>
|
||||||
|
</form>
|
||||||
|
<hr />
|
||||||
|
New? <Link prefetch href='/create-account'><a>Create account</a></Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
// withData gives us server-side graphql queries before rendering
|
||||||
|
withData,
|
||||||
|
// withApollo exposes `this.props.client` used when logging out
|
||||||
|
withApollo,
|
||||||
|
graphql(
|
||||||
|
// The `signinUser` mutation is provided by graph.cool by default
|
||||||
|
gql`
|
||||||
|
mutation Signin($email: String!, $password: String!) {
|
||||||
|
signinUser(email: { email: $email, password: $password }) {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
// Use an unambiguous name for use in the `props` section below
|
||||||
|
name: 'signinWithEmail',
|
||||||
|
// Apollo's way of injecting new props which are passed to the component
|
||||||
|
props: ({
|
||||||
|
signinWithEmail,
|
||||||
|
// `client` is provided by the `withApollo` HOC
|
||||||
|
ownProps: { client }
|
||||||
|
}) => ({
|
||||||
|
// `signin` is the name of the prop passed to the component
|
||||||
|
signin: (event) => {
|
||||||
|
/* global FormData */
|
||||||
|
const data = new FormData(event.target)
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
signinWithEmail({
|
||||||
|
variables: {
|
||||||
|
email: data.get('email'),
|
||||||
|
password: data.get('password')
|
||||||
|
}
|
||||||
|
}).then(({ data: { signinUser: { token } } }) => {
|
||||||
|
// Store the token in cookie
|
||||||
|
document.cookie = cookie.serialize('token', token, {
|
||||||
|
maxAge: 30 * 24 * 60 * 60 // 30 days
|
||||||
|
})
|
||||||
|
|
||||||
|
// Force a reload of all the current queries now that the user is
|
||||||
|
// logged in
|
||||||
|
client.resetStore().then(() => {
|
||||||
|
// Now redirect to the homepage
|
||||||
|
redirect({}, '/')
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
// Something went wrong, such as incorrect password, or no network
|
||||||
|
// available, etc.
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)(Signin)
|
22
examples/with-apollo-auth/project.graphcool
Normal file
22
examples/with-apollo-auth/project.graphcool
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# projectId: cj3h80ffbllm20162alevpcby
|
||||||
|
# version: 3
|
||||||
|
|
||||||
|
type File implements Node {
|
||||||
|
contentType: String!
|
||||||
|
createdAt: DateTime!
|
||||||
|
id: ID! @isUnique
|
||||||
|
name: String!
|
||||||
|
secret: String! @isUnique
|
||||||
|
size: Int!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
url: String! @isUnique
|
||||||
|
}
|
||||||
|
|
||||||
|
type User implements Node {
|
||||||
|
createdAt: DateTime!
|
||||||
|
email: String @isUnique
|
||||||
|
id: ID! @isUnique
|
||||||
|
name: String!
|
||||||
|
password: String
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
41
examples/with-apollo-auth/test/shared-apollo.js
Normal file
41
examples/with-apollo-auth/test/shared-apollo.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
const clearRequire = require('clear-require')
|
||||||
|
const glob = require('glob')
|
||||||
|
const test = require('ava')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motivations:
|
||||||
|
*
|
||||||
|
* - Client-side getInitialProps() wont have access to the apollo client for
|
||||||
|
* that page (because it's not shared across page bundles), so wont be able to
|
||||||
|
* reset the state, leaving all the logged in user data there :(
|
||||||
|
* - So, we have to have a shared module. BUT; next's code splitting means the
|
||||||
|
* bundle for each page will include its own copy of the module, _unless it's
|
||||||
|
* inlcuded in every page_.
|
||||||
|
* - https://github.com/zeit/next.js/issues/659#issuecomment-271824223
|
||||||
|
* - https://github.com/zeit/next.js/issues/1635#issuecomment-292236785
|
||||||
|
* - Therefore, this test ensures that every page includes that module, and
|
||||||
|
* hence it will be shared across every page, giving us a global store in
|
||||||
|
* Apollo that we can clear, etc
|
||||||
|
*/
|
||||||
|
|
||||||
|
const apolloFilePath = require.resolve('../lib/init-apollo')
|
||||||
|
|
||||||
|
test.beforeEach(() => {
|
||||||
|
// Clean up the cache
|
||||||
|
clearRequire.all()
|
||||||
|
})
|
||||||
|
|
||||||
|
glob.sync('./pages/**/*.js').forEach((file) => {
|
||||||
|
test(`.${file} imports shared apollo module`, (t) => {
|
||||||
|
t.falsy(require.cache[apolloFilePath])
|
||||||
|
|
||||||
|
try {
|
||||||
|
require(`.${file}`)
|
||||||
|
} catch (error) {
|
||||||
|
// Don't really care if it fails to execute, etc, just want to be
|
||||||
|
// certain the expected require call was made
|
||||||
|
}
|
||||||
|
|
||||||
|
t.truthy(require.cache[apolloFilePath])
|
||||||
|
})
|
||||||
|
})
|
|
@ -33,6 +33,7 @@ const upvotePost = gql`
|
||||||
mutation updatePost($id: ID!, $votes: Int) {
|
mutation updatePost($id: ID!, $votes: Int) {
|
||||||
updatePost(id: $id, votes: $votes) {
|
updatePost(id: $id, votes: $votes) {
|
||||||
id
|
id
|
||||||
|
__typename
|
||||||
votes
|
votes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +44,9 @@ export default graphql(upvotePost, {
|
||||||
upvote: (id, votes) => mutate({
|
upvote: (id, votes) => mutate({
|
||||||
variables: { id, votes },
|
variables: { id, votes },
|
||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
|
__typename: 'Mutation',
|
||||||
updatePost: {
|
updatePost: {
|
||||||
|
__typename: 'Post',
|
||||||
id: ownProps.id,
|
id: ownProps.id,
|
||||||
votes: ownProps.votes + 1
|
votes: ownProps.votes + 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { ApolloProvider, getDataFromTree } from 'react-apollo'
|
import { ApolloProvider, getDataFromTree } from 'react-apollo'
|
||||||
|
import Head from 'next/head'
|
||||||
import initApollo from './initApollo'
|
import initApollo from './initApollo'
|
||||||
|
|
||||||
// Gets the display name of a JSX component for dev tools
|
// Gets the display name of a JSX component for dev tools
|
||||||
|
@ -30,7 +31,6 @@ export default ComposedComponent => {
|
||||||
const apollo = initApollo()
|
const apollo = initApollo()
|
||||||
// Provide the `url` prop data in case a GraphQL query uses it
|
// Provide the `url` prop data in case a GraphQL query uses it
|
||||||
const url = {query: ctx.query, pathname: ctx.pathname}
|
const url = {query: ctx.query, pathname: ctx.pathname}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Run all GraphQL queries
|
// Run all GraphQL queries
|
||||||
await getDataFromTree(
|
await getDataFromTree(
|
||||||
|
@ -43,6 +43,9 @@ export default ComposedComponent => {
|
||||||
// Handle them in components via the data.error prop:
|
// Handle them in components via the data.error prop:
|
||||||
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
|
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
|
||||||
}
|
}
|
||||||
|
// getDataFromTree does not call componentWillUnmount
|
||||||
|
// head side effect therefore need to be cleared manually
|
||||||
|
Head.rewind()
|
||||||
|
|
||||||
// Extract query data from the Apollo store
|
// Extract query data from the Apollo store
|
||||||
const state = apollo.getInitialState()
|
const state = apollo.getInitialState()
|
||||||
|
|
4
examples/with-babel-macros/.babelrc
Normal file
4
examples/with-babel-macros/.babelrc
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"presets": ["react"],
|
||||||
|
"plugins": ["babel-macros"]
|
||||||
|
}
|
39
examples/with-babel-macros/README.md
Normal file
39
examples/with-babel-macros/README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-babel-macros)
|
||||||
|
|
||||||
|
# Example app with [babel-macros](https://github.com/kentcdodds/babel-macros)
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-babel-macros
|
||||||
|
cd with-babel-macros
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This example features how to configure and use [`babel-macros`](https://github.com/kentcdodds/babel-macros) which allows you
|
||||||
|
to easily add babel plugins which export themselves as a macro without needing
|
||||||
|
to configure them.
|
||||||
|
|
||||||
|
You'll notice the configuration in `.babelrc` includes the `babel-macros`
|
||||||
|
plugin, then we can use the `preval.macro` in `pages/index.js` to pre-evaluate
|
||||||
|
code at build-time. `preval.macro` is effectively transforming our code, but
|
||||||
|
we didn't have to configure it to make that happen!
|
||||||
|
|
||||||
|
Specifically what we're doing is we're prevaling the username of the user who
|
||||||
|
ran the build.
|
22
examples/with-babel-macros/package.json
Normal file
22
examples/with-babel-macros/package.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "with-babel-macros",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "using next.js with babel-macros",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "latest",
|
||||||
|
"react": "^15.4.2",
|
||||||
|
"react-dom": "^15.4.2"
|
||||||
|
},
|
||||||
|
"author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-macros": "^0.5.1",
|
||||||
|
"babel-preset-react": "^6.24.1",
|
||||||
|
"preval.macro": "^1.0.1"
|
||||||
|
}
|
||||||
|
}
|
19
examples/with-babel-macros/pages/_document.js
Normal file
19
examples/with-babel-macros/pages/_document.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Document, {Head, Main, NextScript} from 'next/document'
|
||||||
|
|
||||||
|
export default class MyDocument extends Document {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<Head>
|
||||||
|
<title>With babel-macros</title>
|
||||||
|
<style dangerouslySetInnerHTML={{__html: this.props.css}} />
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
21
examples/with-babel-macros/pages/index.js
Normal file
21
examples/with-babel-macros/pages/index.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from 'react'
|
||||||
|
import preval from 'preval.macro'
|
||||||
|
|
||||||
|
const whoami = preval`
|
||||||
|
const userInfo = require('os').userInfo()
|
||||||
|
module.exports = userInfo.username
|
||||||
|
`
|
||||||
|
|
||||||
|
export default WhoAmI
|
||||||
|
|
||||||
|
function WhoAmI () {
|
||||||
|
return (
|
||||||
|
<div style={{display: 'flex', justifyContent: 'center'}}>
|
||||||
|
<h1>
|
||||||
|
<pre>
|
||||||
|
whoami: {whoami}
|
||||||
|
</pre>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
7
examples/with-custom-reverse-proxy/.babelrc
Normal file
7
examples/with-custom-reverse-proxy/.babelrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"next/babel",
|
||||||
|
"latest",
|
||||||
|
"stage-0"
|
||||||
|
]
|
||||||
|
}
|
3
examples/with-custom-reverse-proxy/.eslintrc
Normal file
3
examples/with-custom-reverse-proxy/.eslintrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "standard"
|
||||||
|
}
|
52
examples/with-custom-reverse-proxy/README.md
Normal file
52
examples/with-custom-reverse-proxy/README.md
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This example applies this gist https://gist.github.com/jamsesso/67fd937b74989dc52e33 to Nextjs and provides:
|
||||||
|
|
||||||
|
* Reverse proxy in development mode by add `http-proxy-middleware` to custom server
|
||||||
|
* NOT a recommended approach to production scale (hence explicit dev flag) as we should scope proxy as outside UI applications and have separate web server taking care of that.
|
||||||
|
|
||||||
|
Sorry for the extra packages. I belong to the minority camp of writing ES6 code on Windows developers. Essentially you only need `http-proxy-middleware` on top of bare-bone Nextjs setup to run this example.
|
||||||
|
|
||||||
|
## How to run it
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i; npm run build; npm run dev;
|
||||||
|
```
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
Take any random query string to the index page and does a GET to `/api/<query string>` which gets routed internally to `https://swapi.co/api/<query string>`, or any API endpoint you wish to configure through the proxy.
|
||||||
|
|
||||||
|
## Expectation
|
||||||
|
|
||||||
|
```
|
||||||
|
/api/people/2 routed to https://swapi.co/api/people/2
|
||||||
|
Try Reset
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "C-3PO",
|
||||||
|
"height": "167",
|
||||||
|
"mass": "75",
|
||||||
|
"hair_color": "n/a",
|
||||||
|
"skin_color": "gold",
|
||||||
|
"eye_color": "yellow",
|
||||||
|
"birth_year": "112BBY",
|
||||||
|
"gender": "n/a",
|
||||||
|
"homeworld": "https://swapi.co/api/planets/1/",
|
||||||
|
"films": [
|
||||||
|
"https://swapi.co/api/films/2/",
|
||||||
|
"https://swapi.co/api/films/5/",
|
||||||
|
"https://swapi.co/api/films/4/",
|
||||||
|
"https://swapi.co/api/films/6/",
|
||||||
|
"https://swapi.co/api/films/3/",
|
||||||
|
"https://swapi.co/api/films/1/"
|
||||||
|
],
|
||||||
|
"species": [
|
||||||
|
"https://swapi.co/api/species/2/"
|
||||||
|
],
|
||||||
|
"vehicles": [],
|
||||||
|
"starships": [],
|
||||||
|
"created": "2014-12-10T15:10:51.357000Z",
|
||||||
|
"edited": "2014-12-20T21:17:50.309000Z",
|
||||||
|
"url": "https://swapi.co/api/people/2/"
|
||||||
|
}
|
||||||
|
```
|
25
examples/with-custom-reverse-proxy/package.json
Normal file
25
examples/with-custom-reverse-proxy/package.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "with-custom-reverse-proxy",
|
||||||
|
"engines": {
|
||||||
|
"node": "7.x.x"
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.15.3",
|
||||||
|
"next": "^3.0.1-beta.13",
|
||||||
|
"react": "^15.5.4",
|
||||||
|
"react-dom": "^15.5.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-preset-latest": "^6.24.1",
|
||||||
|
"babel-preset-stage-0": "^6.24.1",
|
||||||
|
"babel-register": "^6.24.1",
|
||||||
|
"cross-env": "^5.0.1",
|
||||||
|
"http-proxy-middleware": "^0.17.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "cross-env NODE_ENV=development PORT=3000 node server.js",
|
||||||
|
"build": "next build",
|
||||||
|
"prod": "cross-env NODE_ENV=production PORT=3000 node server.js"
|
||||||
|
}
|
||||||
|
}
|
45
examples/with-custom-reverse-proxy/pages/index.js
Normal file
45
examples/with-custom-reverse-proxy/pages/index.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default class extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = { response: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getInitialProps ({ pathname, query }) {
|
||||||
|
return {
|
||||||
|
pathname,
|
||||||
|
query,
|
||||||
|
queryString: Object.keys(query).join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount () {
|
||||||
|
const response = JSON.stringify(
|
||||||
|
await window
|
||||||
|
.fetch(`/api/${this.props.queryString}`)
|
||||||
|
.then(response => response.json().then(data => data)),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
this.setState({ response })
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<content>
|
||||||
|
<p>
|
||||||
|
/api/{this.props.queryString} routed to https://swapi.co/api/{this.props.queryString}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href='?people/2'>Try</a>
|
||||||
|
|
||||||
|
<a href='/'>Reset</a>
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
{this.state.response ? this.state.response : 'Loading...'}
|
||||||
|
</pre>
|
||||||
|
</content>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
50
examples/with-custom-reverse-proxy/server.es6.js
Normal file
50
examples/with-custom-reverse-proxy/server.es6.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import express from 'express'
|
||||||
|
import next from 'next'
|
||||||
|
|
||||||
|
const devProxy = {
|
||||||
|
'/api': {
|
||||||
|
target: 'https://swapi.co/api/',
|
||||||
|
pathRewrite: {'^/api': '/'},
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = process.env.PORT || 3000
|
||||||
|
const env = process.env.NODE_ENV
|
||||||
|
const dev = env !== 'production'
|
||||||
|
const app = next({
|
||||||
|
dir: '.', // base directory where everything is, could move to src later
|
||||||
|
dev
|
||||||
|
})
|
||||||
|
|
||||||
|
const handle = app.getRequestHandler()
|
||||||
|
|
||||||
|
let server
|
||||||
|
app
|
||||||
|
.prepare()
|
||||||
|
.then(() => {
|
||||||
|
server = express()
|
||||||
|
|
||||||
|
// Set up the proxy.
|
||||||
|
if (dev && devProxy) {
|
||||||
|
const proxyMiddleware = require('http-proxy-middleware')
|
||||||
|
Object.keys(devProxy).forEach(function (context) {
|
||||||
|
server.use(proxyMiddleware(context, devProxy[context]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default catch-all handler to allow Next.js to handle all other routes
|
||||||
|
server.all('*', (req, res) => handle(req, res))
|
||||||
|
|
||||||
|
server.listen(port, err => {
|
||||||
|
if (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
console.log(`> Ready on port ${port} [${env}]`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log('An error occurred, unable to start the server')
|
||||||
|
console.log(err)
|
||||||
|
})
|
2
examples/with-custom-reverse-proxy/server.js
Normal file
2
examples/with-custom-reverse-proxy/server.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
require('babel-register')
|
||||||
|
module.exports = require('./server.es6.js')
|
13
examples/with-dotenv/.babelrc
Normal file
13
examples/with-dotenv/.babelrc
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"next/babel",
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"development": {
|
||||||
|
"plugins": ["inline-dotenv"]
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"plugins": ["transform-inline-environment-variables"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
examples/with-dotenv/.env
Normal file
1
examples/with-dotenv/.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TEST=it works!
|
1
examples/with-dotenv/.env.production
Normal file
1
examples/with-dotenv/.env.production
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TEST=it works!
|
31
examples/with-dotenv/README.md
Normal file
31
examples/with-dotenv/README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-dotenv)
|
||||||
|
|
||||||
|
# With Dotenv example
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-dotenv
|
||||||
|
cd with-dotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This example shows the most basic idea of babel replacement from multiple environment. We have 1 env variable: `TEST` which will be replaced in development env and in production env with different babel plugin. In local development, babel reads .env file and replace process.env.* in your nextjs files. In production env (such as heroku), babel reads the ENV and replace process.env.* in your nextjs files. Thus no more needed to commit your secrets anymore.
|
||||||
|
|
||||||
|
Of course, please put .env* in your .gitignore when using this example locally.
|
17
examples/with-dotenv/package.json
Normal file
17
examples/with-dotenv/package.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "with-dotenv",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "latest",
|
||||||
|
"react": "^15.4.2",
|
||||||
|
"react-dom": "^15.4.2",
|
||||||
|
"babel-plugin-inline-dotenv": "^1.1.1",
|
||||||
|
"babel-plugin-transform-inline-environment-variables": "^0.1.1"
|
||||||
|
},
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
3
examples/with-dotenv/pages/index.js
Normal file
3
examples/with-dotenv/pages/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default () => (
|
||||||
|
<div>{ process.env.TEST }</div>
|
||||||
|
)
|
27
examples/with-dynamic-import/README.md
Normal file
27
examples/with-dynamic-import/README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Example app with dynamic-imports
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://codeload.github.com/zeit/next.js/tar.gz/v3-beta | tar -xz --strip=2 next.js-3-beta/examples/with-dynamic-import
|
||||||
|
cd with-dynamic-import
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This examples shows how to dynamically import modules via [`import()`](https://github.com/tc39/proposal-dynamic-import) API
|
19
examples/with-dynamic-import/components/Counter.js
Normal file
19
examples/with-dynamic-import/components/Counter.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
export default class Counter extends React.Component {
|
||||||
|
add () {
|
||||||
|
count += 1
|
||||||
|
this.forceUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>Count is: {count}</p>
|
||||||
|
<button onClick={() => this.add()}>Add</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
19
examples/with-dynamic-import/components/Header.js
Normal file
19
examples/with-dynamic-import/components/Header.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<div>
|
||||||
|
<Link href='/'>
|
||||||
|
<a style={styles.a} >Home</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href='/about'>
|
||||||
|
<a style={styles.a} >About</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
a: {
|
||||||
|
marginRight: 10
|
||||||
|
}
|
||||||
|
}
|
3
examples/with-dynamic-import/components/hello1.js
Normal file
3
examples/with-dynamic-import/components/hello1.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default () => (
|
||||||
|
<p>Hello World 1 (imported dynamiclly) </p>
|
||||||
|
)
|
3
examples/with-dynamic-import/components/hello2.js
Normal file
3
examples/with-dynamic-import/components/hello2.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default () => (
|
||||||
|
<p>Hello World 2 (imported dynamiclly) </p>
|
||||||
|
)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue