diff --git a/README-zh-CN.md b/README-zh-CN.md index 2e3a7a37..356cd3c2 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -765,7 +765,7 @@ class MyLink extends React.Component { const { router } = this.props router.prefetch('/dynamic') } - + render() { const { router } = this.props return ( @@ -773,7 +773,7 @@ class MyLink extends React.Component { setTimeout(() => router.push('/dynamic'), 100)}> A route transition will happen after 100ms - + ) } } @@ -1343,7 +1343,7 @@ module.exports = { ```js // Example next.config.js for adding a loader that depends on babel-loader -// This source was taken from the @zeit/next-mdx plugin source: +// This source was taken from the @zeit/next-mdx plugin source: // https://github.com/zeit/next-plugins/blob/master/packages/next-mdx module.exports = { webpack: (config, {}) => { @@ -1464,7 +1464,7 @@ module.exports = { } ``` -注意:Next.js 运行时将会自动添加前缀,但是对于`/static`是没有效果的,如果你想这些静态资源也能使用 CDN,你需要自己添加前缀。有一个方法可以判断你的环境来加前缀,如 [in this example](https://github.com/zeit/next.js/tree/master/examples/with-universal-configuration)。 +注意:Next.js 运行时将会自动添加前缀,但是对于`/static`是没有效果的,如果你想这些静态资源也能使用 CDN,你需要自己添加前缀。有一个方法可以判断你的环境来加前缀,如 [in this example](https://github.com/zeit/next.js/tree/master/examples/with-universal-configuration-build-time)。 ## 项目部署 diff --git a/examples/with-hashed-statics/README.md b/examples/with-hashed-statics/README.md index 6fc9f9fd..f4d561f3 100644 --- a/examples/with-hashed-statics/README.md +++ b/examples/with-hashed-statics/README.md @@ -41,4 +41,4 @@ now This example shows how to import images, videos, etc. from `/static` and get the URL with a hash query allowing to use better cache without problems. -This example supports `.svg`, `.png` and `.txt` extensions, but it can be configured to support any possible extension changing the `extensions` array in the `.babelrc` file. \ No newline at end of file +This example supports `.svg`, `.png` and `.txt` extensions, but it can be configured to support any possible extension changing the `extensions` array in the `next.config.js` [file](https://github.com/zeit/next.js/blob/canary/examples/with-hashed-statics/next.config.js#L4). diff --git a/examples/with-sentry/next.config.js b/examples/with-sentry/next.config.js new file mode 100644 index 00000000..61b2b70f --- /dev/null +++ b/examples/with-sentry/next.config.js @@ -0,0 +1,23 @@ +const webpack = require('webpack') +const nextSourceMaps = require('@zeit/next-source-maps')() + +const SENTRY_DSN = '' + +module.exports = nextSourceMaps({ + webpack: (config, { dev, isServer, buildId }) => { + if (!dev) { + config.plugins.push( + new webpack.DefinePlugin({ + 'process.env.SENTRY_DSN': JSON.stringify(SENTRY_DSN), + 'process.env.SENTRY_RELEASE': JSON.stringify(buildId) + }) + ) + } + + if (!isServer) { + config.resolve.alias['@sentry/node'] = '@sentry/browser' + } + + return config + } +}) diff --git a/examples/with-sentry/pages/index.js b/examples/with-sentry/pages/index.js index 826020e3..cde8a5ec 100644 --- a/examples/with-sentry/pages/index.js +++ b/examples/with-sentry/pages/index.js @@ -1,23 +1,48 @@ import React from 'react' +import Link from 'next/link' class Index extends React.Component { + static getInitialProps ({ query, req }) { + if (query.raiseError) { + throw new Error('Error in getInitialProps') + } + } + state = { raiseError: false } componentDidUpdate () { - if (this.state.raiseError) { - throw new Error('Houston, we have a problem') + if (this.state.raiseErrorInUpdate) { + throw new Error('Error in componentDidUpdate') } } - raiseError = () => this.setState({ raiseError: true }) + raiseErrorInUpdate = () => this.setState({ raiseErrorInUpdate: '1' }) + raiseErrorInRender = () => this.setState({ raiseErrorInRender: '1' }) render () { + if (this.state.raiseErrorInRender) { + throw new Error('Error in render') + } + return (

Index page

- +
) } diff --git a/examples/with-sentry/server.js b/examples/with-sentry/server.js new file mode 100644 index 00000000..7ade81e4 --- /dev/null +++ b/examples/with-sentry/server.js @@ -0,0 +1,66 @@ +const next = require('next') +const express = require('express') +const cookieParser = require('cookie-parser') +const Sentry = require('@sentry/node') +const uuidv4 = require('uuid/v4') +const port = parseInt(process.env.PORT, 10) || 3000 +const dev = process.env.NODE_ENV !== 'production' +const app = next({ dev }) +const handle = app.getRequestHandler() + +require('./utils/sentry') + +app.prepare() + .then(() => { + const server = express() + + // This attaches request information to sentry errors + server.use(Sentry.Handlers.requestHandler()) + + server.use(cookieParser()) + + server.use((req, res, next) => { + const htmlPage = + !req.path.match(/^\/(_next|static)/) && + !req.path.match(/\.(js|map)$/) && + req.accepts('text/html', 'text/css', 'image/png') === 'text/html' + + if (!htmlPage) { + next() + return + } + + if (!req.cookies.sid || req.cookies.sid.length === 0) { + req.cookies.sid = uuidv4() + res.cookie('sid', req.cookies.sid) + } + + next() + }) + + // In production we don't want to serve sourcemaps for anyone + if (!dev) { + const hasSentryToken = !!process.env.SENTRY_TOKEN + server.get(/\.map$/, (req, res, next) => { + if (hasSentryToken && req.headers['x-sentry-token'] !== process.env.SENTRY_TOKEN) { + res + .status(401) + .send( + 'Authentication access token is required to access the source map.' + ) + return + } + next() + }) + } + + server.get('*', (req, res) => handle(req, res)) + + // This handles errors if they are thrown before raching the app + server.use(Sentry.Handlers.errorHandler()) + + server.listen(port, err => { + if (err) throw err + console.log(`> Ready on http://localhost:${port}`) + }) + }) diff --git a/examples/with-sentry/utils/sentry.js b/examples/with-sentry/utils/sentry.js new file mode 100644 index 00000000..458c9019 --- /dev/null +++ b/examples/with-sentry/utils/sentry.js @@ -0,0 +1,63 @@ +const Sentry = require('@sentry/node') +const Cookie = require('js-cookie') + +if (process.env.SENTRY_DSN) { + Sentry.init({ + dsn: process.env.SENTRY_DSN, + release: process.env.SENTRY_RELEASE, + maxBreadcrumbs: 50, + attachStacktrace: true + }) +} + +function captureException (err, { req, res, errorInfo, query, pathname }) { + Sentry.configureScope(scope => { + if (err.message) { + // De-duplication currently doesn't work correctly for SSR / browser errors + // so we force deduplication by error message if it is present + scope.setFingerprint([err.message]) + } + + if (err.statusCode) { + scope.setExtra('statusCode', err.statusCode) + } + + if (res && res.statusCode) { + scope.setExtra('statusCode', res.statusCode) + } + + if (process.browser) { + scope.setTag('ssr', false) + scope.setExtra('query', query) + scope.setExtra('pathname', pathname) + + // On client-side we use js-cookie package to fetch it + const sessionId = Cookie.get('sid') + if (sessionId) { + scope.setUser({ id: sessionId }) + } + } else { + scope.setTag('ssr', true) + scope.setExtra('url', req.url) + scope.setExtra('method', req.method) + scope.setExtra('headers', req.headers) + scope.setExtra('params', req.params) + scope.setExtra('query', req.query) + + // On server-side we take session cookie directly from request + if (req.cookies.sid) { + scope.setUser({ id: req.cookies.sid }) + } + } + + if (errorInfo) { + scope.setExtra('componentStack', errorInfo.componentStack) + } + }) + + Sentry.captureException(err) +} + +module.exports = { + captureException +} diff --git a/examples/with-tailwindcss/styles/index.css b/examples/with-tailwindcss/styles/index.css index 2d26f976..bcb36071 100644 --- a/examples/with-tailwindcss/styles/index.css +++ b/examples/with-tailwindcss/styles/index.css @@ -1,6 +1,7 @@ @import "./button.css"; @tailwind preflight; +@tailwind components; @tailwind utilities; .hero { diff --git a/lerna.json b/lerna.json index 31fea130..092c6aae 100644 --- a/lerna.json +++ b/lerna.json @@ -10,9 +10,12 @@ }, "publish": { "npmClient": "npm", - "allowBranch": "canary", + "allowBranch": [ + "master", + "canary" + ], "registry": "https://registry.npmjs.org/" } }, - "version": "8.0.0-canary.24" + "version": "8.0.1-canary.0" } diff --git a/package.json b/package.json index 0ffc46f5..2f363c35 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "typescript": "lerna run typescript", "prepublish": "lerna run prepublish", "publish-canary": "lerna version prerelease --preid canary --force-publish && release --pre", + "publish-stable": "lerna version --force-publish", "lint-staged": "lint-staged" }, "pre-commit": "lint-staged", diff --git a/packages/next-server/package.json b/packages/next-server/package.json index 7e1235d5..2aad2e31 100644 --- a/packages/next-server/package.json +++ b/packages/next-server/package.json @@ -1,6 +1,6 @@ { "name": "next-server", - "version": "8.0.0-canary.24", + "version": "8.0.1-canary.0", "main": "./index.js", "license": "MIT", "files": [ diff --git a/packages/next/README.md b/packages/next/README.md index 0960df74..a1bb7eb6 100644 --- a/packages/next/README.md +++ b/packages/next/README.md @@ -836,7 +836,7 @@ You can add `prefetch` prop to any `` and Next.js will prefetch those page ```jsx import Link from 'next/link' -function Header() { +function Header() { return (