diff --git a/.gitignore b/.gitignore index 1f02e940..8f980adb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,12 @@ dist node_modules # logs -npm-debug.log +*.log # coverage .nyc_output coverage +# test output +test/**/out .DS_Store diff --git a/bin/next b/bin/next index 79f635b7..234b3d5b 100755 --- a/bin/next +++ b/bin/next @@ -22,6 +22,7 @@ const commands = new Set([ 'init', 'build', 'start', + 'export', defaultCommand ]) diff --git a/bin/next-export b/bin/next-export new file mode 100644 index 00000000..37508242 --- /dev/null +++ b/bin/next-export @@ -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] + + 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) + }) diff --git a/client/index.js b/client/index.js index cbf237c4..419efa33 100644 --- a/client/index.js +++ b/client/index.js @@ -30,6 +30,8 @@ const { location } = window +const asPath = getURL() + const pageLoader = new PageLoader(buildId, assetPrefix) window.__NEXT_LOADED_PAGES__.forEach(({ route, fn }) => { pageLoader.registerPage(route, fn) @@ -68,7 +70,7 @@ export default async () => { Component = ErrorComponent } - router = createRouter(pathname, query, getURL(), { + router = createRouter(pathname, query, asPath, { pageLoader, Component, ErrorComponent, @@ -119,7 +121,7 @@ export async function renderError (error) { console.error(errorMessage) if (prod) { - const initProps = { err: error, pathname, query } + const initProps = { err: error, pathname, query, asPath } const props = await loadGetInitialProps(ErrorComponent, initProps) ReactDOM.render(createElement(ErrorComponent, props), errorContainer) } else { @@ -132,8 +134,8 @@ async function doRender ({ Component, props, hash, err, emitter }) { Component !== ErrorComponent && lastAppProps.Component === ErrorComponent) { // fetch props if ErrorComponent was replaced with a page component by HMR - const { pathname, query } = router - props = await loadGetInitialProps(Component, { err, pathname, query }) + const { pathname, query, asPath } = router + props = await loadGetInitialProps(Component, { err, pathname, query, asPath }) } if (emitter) { diff --git a/examples/root-static-files/README.md b/examples/root-static-files/README.md new file mode 100644 index 00000000..adcc63cc --- /dev/null +++ b/examples/root-static-files/README.md @@ -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/custom-server) + +# Root static files 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/custom-server +cd custom-server +``` + +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 demonstrates how to serve files such as /robots.txt and /sitemap.xml from the root. diff --git a/examples/root-static-files/package.json b/examples/root-static-files/package.json new file mode 100644 index 00000000..7c5f57b9 --- /dev/null +++ b/examples/root-static-files/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "dev": "node server.js", + "build": "next build", + "start": "NODE_ENV=production node server.js" + }, + "dependencies": { + "next": "latest", + "react": "^15.5.4", + "react-dom": "^15.5.4" + } +} diff --git a/examples/root-static-files/pages/index.js b/examples/root-static-files/pages/index.js new file mode 100644 index 00000000..2bcdac86 --- /dev/null +++ b/examples/root-static-files/pages/index.js @@ -0,0 +1,7 @@ +export default () => ( + +) diff --git a/examples/root-static-files/server.js b/examples/root-static-files/server.js new file mode 100644 index 00000000..7f26bd60 --- /dev/null +++ b/examples/root-static-files/server.js @@ -0,0 +1,30 @@ +const { createServer } = require('http') +const { parse } = require('url') +const next = require('next') +const { join } = require('path') + +const dev = process.env.NODE_ENV !== 'production' +const app = next({ dev }) +const handle = app.getRequestHandler() + +app.prepare() +.then(() => { + createServer((req, res) => { + const parsedUrl = parse(req.url, true) + const rootStaticFiles = [ + '/robots.txt', + '/sitemap.xml', + '/favicon.ico' + ] + if (rootStaticFiles.indexOf(parsedUrl.pathname) > -1) { + const path = join(__dirname, 'static', parsedUrl.pathname) + app.serveStatic(req, res, path) + } else { + handle(req, res, parsedUrl) + } + }) + .listen(3000, (err) => { + if (err) throw err + console.log('> Ready on http://localhost:3000') + }) +}) diff --git a/examples/root-static-files/static/favicon.ico b/examples/root-static-files/static/favicon.ico new file mode 100644 index 00000000..cdeb3a7c Binary files /dev/null and b/examples/root-static-files/static/favicon.ico differ diff --git a/examples/root-static-files/static/robots.txt b/examples/root-static-files/static/robots.txt new file mode 100644 index 00000000..1f53798b --- /dev/null +++ b/examples/root-static-files/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/examples/root-static-files/static/sitemap.xml b/examples/root-static-files/static/sitemap.xml new file mode 100644 index 00000000..a1384f83 --- /dev/null +++ b/examples/root-static-files/static/sitemap.xml @@ -0,0 +1,6 @@ + + + + http://www.example.com/foo.html + + diff --git a/examples/svg-components/package.json b/examples/svg-components/package.json index c744ec88..1bbd46bf 100644 --- a/examples/svg-components/package.json +++ b/examples/svg-components/package.json @@ -7,7 +7,7 @@ "start": "next start" }, "dependencies": { - "next": "beta" + "next": "latest" }, "devDependencies": { "babel-plugin-inline-react-svg": "^0.2.0" diff --git a/examples/with-aphrodite/pages/_document.js b/examples/with-aphrodite/pages/_document.js index a4f6cda2..682000bc 100644 --- a/examples/with-aphrodite/pages/_document.js +++ b/examples/with-aphrodite/pages/_document.js @@ -4,15 +4,31 @@ import { StyleSheetServer } from 'aphrodite' export default class MyDocument extends Document { static async getInitialProps ({ renderPage }) { const { html, css } = StyleSheetServer.renderStatic(() => renderPage()) - return { ...html, css } + const ids = css.renderedClassNames + return { ...html, css, ids } + } + + constructor (props) { + super(props) + /* Take the renderedClassNames from aphrodite (as generated + in getInitialProps) and assign them to __NEXT_DATA__ so that they + are accessible to the client for rehydration. */ + const { __NEXT_DATA__, ids } = props + if (ids) { + __NEXT_DATA__.ids = this.props.ids + } } render () { + /* Make sure to use data-aphrodite attribute in the style tag here + so that aphrodite knows which style tag it's in control of when + the client goes to render styles. If you don't you'll get a second + +

AddCount: {count}

+ + + ) + } +} + +const mapStateToProps = ({ count }) => ({ count }) + +const mapDispatchToProps = (dispatch) => { + return { + addCount: bindActionCreators(addCount, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddCount) diff --git a/examples/with-redux/components/Page.js b/examples/with-redux/components/Page.js index f7941894..3532e3fb 100644 --- a/examples/with-redux/components/Page.js +++ b/examples/with-redux/components/Page.js @@ -1,12 +1,14 @@ import Link from 'next/link' import { connect } from 'react-redux' import Clock from './Clock' +import AddCount from './AddCount' export default connect(state => state)(({ title, linkTo, lastUpdate, light }) => { return (

{title}

+ diff --git a/examples/with-redux/pages/index.js b/examples/with-redux/pages/index.js index 0663cfb7..be66747b 100644 --- a/examples/with-redux/pages/index.js +++ b/examples/with-redux/pages/index.js @@ -1,16 +1,19 @@ import React from 'react' -import { initStore, startClock } from '../store' +import { bindActionCreators } from 'redux' +import { initStore, startClock, addCount, serverRenderClock } from '../store' import withRedux from 'next-redux-wrapper' import Page from '../components/Page' class Counter extends React.Component { static getInitialProps ({ store, isServer }) { - store.dispatch({ type: 'TICK', light: !isServer, ts: Date.now() }) + store.dispatch(serverRenderClock(isServer)) + store.dispatch(addCount()) + return { isServer } } componentDidMount () { - this.timer = this.props.dispatch(startClock()) + this.timer = this.props.startClock() } componentWillUnmount () { @@ -24,4 +27,11 @@ class Counter extends React.Component { } } -export default withRedux(initStore)(Counter) +const mapDispatchToProps = (dispatch) => { + return { + addCount: bindActionCreators(addCount, dispatch), + startClock: bindActionCreators(startClock, dispatch) + } +} + +export default withRedux(initStore, null, mapDispatchToProps)(Counter) diff --git a/examples/with-redux/pages/other.js b/examples/with-redux/pages/other.js index 8152dd13..26bfa708 100644 --- a/examples/with-redux/pages/other.js +++ b/examples/with-redux/pages/other.js @@ -1,20 +1,18 @@ import React from 'react' -import { initStore, startClock } from '../store' +import { bindActionCreators } from 'redux' +import { initStore, startClock, addCount, serverRenderClock } from '../store' import withRedux from 'next-redux-wrapper' import Page from '../components/Page' class Counter extends React.Component { static getInitialProps ({ store, isServer }) { - store.dispatch({ type: 'TICK', light: !isServer, ts: Date.now() }) + store.dispatch(serverRenderClock(isServer)) + store.dispatch(addCount()) return { isServer } } componentDidMount () { - this.timer = this.props.dispatch(startClock()) - } - - componentWillUnmount () { - clearInterval(this.timer) + this.timer = this.props.startClock() } render () { @@ -24,4 +22,11 @@ class Counter extends React.Component { } } -export default withRedux(initStore)(Counter) +const mapDispatchToProps = (dispatch) => { + return { + addCount: bindActionCreators(addCount, dispatch), + startClock: bindActionCreators(startClock, dispatch) + } +} + +export default withRedux(initStore, null, mapDispatchToProps)(Counter) diff --git a/examples/with-redux/store.js b/examples/with-redux/store.js index a15a9f81..71ab2d5e 100644 --- a/examples/with-redux/store.js +++ b/examples/with-redux/store.js @@ -1,17 +1,43 @@ import { createStore, applyMiddleware } from 'redux' import thunkMiddleware from 'redux-thunk' -export const reducer = (state = { lastUpdate: 0, light: false }, action) => { +const exampleInitialState = { + lastUpdate: 0, + light: false, + count: 0 +} + +export const actionTypes = { + ADD: 'ADD', + TICK: 'TICK' +} + +// REDUCERS +export const reducer = (state = exampleInitialState, action) => { switch (action.type) { - case 'TICK': return { lastUpdate: action.ts, light: !!action.light } + case actionTypes.TICK: + return Object.assign({}, state, { lastUpdate: action.ts, light: !!action.light }) + case actionTypes.ADD: + return Object.assign({}, state, { + count: state.count + 1 + }) default: return state } } +// ACTIONS +export const serverRenderClock = (isServer) => dispatch => { + return dispatch({ type: actionTypes.TICK, light: !isServer, ts: Date.now() }) +} + export const startClock = () => dispatch => { return setInterval(() => dispatch({ type: 'TICK', light: true, ts: Date.now() }), 800) } -export const initStore = (initialState) => { +export const addCount = () => dispatch => { + return dispatch({ type: actionTypes.ADD }) +} + +export const initStore = (initialState = exampleInitialState) => { return createStore(reducer, initialState, applyMiddleware(thunkMiddleware)) } diff --git a/examples/with-refnux/README.md b/examples/with-refnux/README.md index 8a469efc..ecc6cb27 100644 --- a/examples/with-refnux/README.md +++ b/examples/with-refnux/README.md @@ -27,7 +27,7 @@ now ## The idea behind the example -This example, just like `with-react` and `with-mobx` examples, shows how to manage a global state in your web-application. +This example, just like `with-redux` and `with-mobx` examples, shows how to manage a global state in your web-application. In this case we are using [refnux](https://github.com/algesten/refnux) which is an alternative, simpler, purely functional store state manager. We have two very similar pages (page1.js, page2.js). They both diff --git a/examples/with-shallow-routing/package.json b/examples/with-shallow-routing/package.json index 5f52e996..95ac6a1c 100644 --- a/examples/with-shallow-routing/package.json +++ b/examples/with-shallow-routing/package.json @@ -7,7 +7,7 @@ "start": "next start" }, "dependencies": { - "next": "next@beta", + "next": "latest", "react": "^15.4.2", "react-dom": "^15.4.2" }, diff --git a/examples/with-styled-components/.babelrc b/examples/with-styled-components/.babelrc new file mode 100644 index 00000000..e0a0a613 --- /dev/null +++ b/examples/with-styled-components/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": [ + "next/babel" + ], + "plugins": [ + ["styled-components", { "ssr": true, "displayName": true, "preprocess": false } ] + ] +} diff --git a/examples/with-styled-components/README.md b/examples/with-styled-components/README.md index 64bcebf8..64f1b125 100644 --- a/examples/with-styled-components/README.md +++ b/examples/with-styled-components/README.md @@ -30,6 +30,4 @@ This example features how you use a different styling solution than [styled-jsx] For this purpose we are extending the `` and injecting the server side rendered styles into the ``. -## Notes: - -- On initial install, you may see a server-side error: `TypeError: Cannot read property 'cssRules' of undefined when using this line of code` until you actually render a `styled-component`. I have submitted a PR to fix this issue with them [here](https://github.com/styled-components/styled-components/pull/391). For the time being, make sure you render at least one `styled-component` when you use this. +# WARNING This example uses styled-components v2 which is currently in BETA diff --git a/examples/with-styled-components/package.json b/examples/with-styled-components/package.json index e0104cac..fd7a1cf9 100644 --- a/examples/with-styled-components/package.json +++ b/examples/with-styled-components/package.json @@ -7,10 +7,12 @@ "start": "next start" }, "dependencies": { + "babel-plugin-styled-components": "^1.1.4", + "babel-preset-stage-0": "^6.24.1", "next": "latest", "react": "^15.4.2", "react-dom": "^15.4.2", - "styled-components": "^1.4.4" + "styled-components": "^2.0.0-17" }, "author": "", "license": "ISC" diff --git a/examples/with-styled-components/pages/_document.js b/examples/with-styled-components/pages/_document.js index 030091d0..77104a49 100644 --- a/examples/with-styled-components/pages/_document.js +++ b/examples/with-styled-components/pages/_document.js @@ -1,23 +1,21 @@ import Document, { Head, Main, NextScript } from 'next/document' -import styleSheet from 'styled-components/lib/models/StyleSheet' +import { ServerStyleSheet } from 'styled-components' export default class MyDocument extends Document { - static async getInitialProps ({ renderPage }) { - const page = renderPage() - const styles = ( - +
) ``` +Please see the [styled-jsx documentation](https://github.com/zeit/styled-jsx) for more examples. + #### CSS-in-JS

Examples - +

It's possible to use any existing CSS-in-JS solution. The simplest one is inline styles: @@ -239,6 +247,7 @@ export default Page - `pathname` - path section of URL - `query` - query string section of URL parsed as an object +- `asPath` - the actual url path - `req` - HTTP request object (server only) - `res` - HTTP response object (server only) - `jsonPageRes` - [Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object (client only) @@ -284,6 +293,7 @@ Each top-level component receives a `url` property with the following API: - `pathname` - `String` of the current path excluding the query string - `query` - `Object` with the parsed query string. Defaults to `{}` +- `asPath` - `String` of the actual path (including the query) shows in the browser - `push(url, as=url)` - performs a `pushState` call with the given url - `replace(url, as=url)` - performs a `replaceState` call with the given url @@ -703,6 +713,8 @@ The `ctx` object is equivalent to the one received in all [`getInitialProps`](#f - `renderPage` (`Function`) a callback that executes the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's [`renderStatic`](https://github.com/Khan/aphrodite#server-side-rendering) +__Note: React-components outside of `
` will not be initialised by the browser. If you need shared components in all your pages (like a menu or a toolbar), do _not_ add application logic here, but take a look at [this example](https://github.com/zeit/next.js/tree/master/examples/layout-component).__ + ### Custom error handling 404 or 500 errors are handled both client and server side by a default component `error.js`. If you wish to override it, define a `_error.js`: @@ -762,9 +774,15 @@ In order to extend our usage of `webpack`, you can define a function that extend module.exports = { webpack: (config, { dev }) => { -    // Perform customizations to config -     -    // Important: return the modified config + // Perform customizations to webpack config + + // Important: return the modified config + return config + }, + webpackDevMiddleware: (config) => { + // Perform customizations to webpack dev middleware config + + // Important: return the modified config return config } } @@ -841,6 +859,79 @@ Next.js can be deployed to other hosting solutions too. Please have a look at th Note: we recommend putting `.next`, or your custom dist folder (Please have a look at ['Custom Config'](You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration.)), in `.npmignore` or `.gitignore`. Otherwise, use `files` or `now.files` to opt-into a whitelist of files you want to deploy (and obviously exclude `.next` or your custom dist folder) +## Static HTML export + +This is a way to run your Next.js app as a standalone static app without any Node.js server. The export app supports almost every feature of Next.js including dyanmic urls, prefetching, preloading and dynamic imports. + +### Usage + +Simply develop your app as you normally do with Next.js. Then create a custom Next.js [config](https://github.com/zeit/next.js#custom-configuration) as shown below: + +```js +// next.config.js +module.exports = { + exportPathMap: function () { + return { + "/": { page: "/" }, + "/about": { page: "/about" }, + "/p/hello-nextjs": { page: "/post", query: { title: "hello-nextjs" } }, + "/p/learn-nextjs": { page: "/post", query: { title: "learn-nextjs" } }, + "/p/deploy-nextjs": { page: "/post", query: { title: "deploy-nextjs" } } + } + }, +} +``` + +In that, you specify what are the pages you need to export as static HTML. + +Then simply run these commands: + +```sh +next build +next export +``` + +For that you may need to add a NPM script to `package.json` like this: + +```json +{ + "scripts": { + "build": "next build && next export" + } +} +``` + +And run it at once with: + +```sh +npm run build +``` + +Then you've a static version of your app in the “out" directory. + +> You can also customize the output directory. For that run `next export -h` for the help. + +Now you can deploy that directory to any static hosting service. + +For an example, simply visit the “out” directory and run following command to deploy your app to [ZEIT now](https://zeit.co/now). + +```sh +now +``` + +### Limitation + +With next export, we build HTML version of your app when you run the command `next export`. In that time, we'll run the `getInitialProps` functions of your pages. + +So, you could only use `pathname`, `query` and `asPath` fields of the `context` object passed to `getInitialProps`. You can't use `req` or `res` fields. + +> Basically, you won't be able to render HTML content dynamically as we pre-build HTML files. If you need that, you need run your app with `next start`. + + +## Recipes + +- [Setting up 301 redirects](https://www.raygesualdo.com/posts/301-redirects-with-nextjs/) + ## FAQ
diff --git a/server/build/babel/preset.js b/server/build/babel/preset.js index 61e3e371..cff127ba 100644 --- a/server/build/babel/preset.js +++ b/server/build/babel/preset.js @@ -9,7 +9,7 @@ const envPlugins = { ] } -const plugins = envPlugins[process.env.NODE_ENV] || [] +const plugins = envPlugins[process.env.NODE_ENV] || envPlugins['development'] module.exports = { presets: [ diff --git a/server/build/plugins/pages-plugin.js b/server/build/plugins/pages-plugin.js index 203fbe60..b8a827f9 100644 --- a/server/build/plugins/pages-plugin.js +++ b/server/build/plugins/pages-plugin.js @@ -12,7 +12,17 @@ export default class PagesPlugin { pages.forEach((chunk) => { const page = compilation.assets[chunk.name] const pageName = matchRouteName.exec(chunk.name)[1] - const routeName = `/${pageName.replace(/[/\\]?index$/, '')}` + let routeName = `/${pageName.replace(/[/\\]?index$/, '')}` + + // We need to convert \ into / when we are in windows + // to get the proper route name + // Here we need to do windows check because it's possible + // to have "\" in the filename in unix. + // Anyway if someone did that, he'll be having issues here. + // But that's something we cannot avoid. + if (/^win/.test(process.platform)) { + routeName = routeName.replace(/\\/g, '/') + } const content = page.source() const newContent = ` diff --git a/server/build/webpack.js b/server/build/webpack.js index 81b7f799..b15262ce 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -75,6 +75,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false, } const plugins = [ + new webpack.IgnorePlugin(/(precomputed)/, /node_modules.+(elliptic)/), new webpack.LoaderOptionsPlugin({ options: { context: dir, @@ -299,10 +300,19 @@ export default async function createCompiler (dir, { dev = false, quiet = false, module: { rules }, - devtool: dev ? 'inline-source-map' : false, + devtool: dev ? 'cheap-module-inline-source-map' : false, performance: { hints: false } } + if (!dev) { + // We do this to use the minified version of React in production. + // This will significant file size redution when turned off uglifyjs. + webpackConfig.resolve.alias = { + 'react': require.resolve('react/dist/react.min.js'), + 'react-dom': require.resolve('react-dom/dist/react-dom.min.js') + } + } + if (config.webpack) { console.log('> Using "webpack" config function defined in next.config.js.') webpackConfig = await config.webpack(webpackConfig, { dev }) diff --git a/server/config.js b/server/config.js index bc5059f5..81c3664b 100644 --- a/server/config.js +++ b/server/config.js @@ -5,6 +5,7 @@ const cache = new Map() const defaultConfig = { webpack: null, + webpackDevMiddleware: null, poweredByHeader: true, distDir: '.next', assetPrefix: '' diff --git a/server/document.js b/server/document.js index ef21c7b8..26c5c18c 100644 --- a/server/document.js +++ b/server/document.js @@ -80,11 +80,12 @@ export class Head extends Component { render () { const { head, styles, __NEXT_DATA__ } = this.context._documentProps - const { pathname, buildId, assetPrefix } = __NEXT_DATA__ + const { pathname, buildId, assetPrefix, nextExport } = __NEXT_DATA__ + const pagePathname = getPagePathname(pathname, nextExport) return - - + + {this.getPreloadDynamicChunks()} {this.getPreloadMainLinks()} {(head || []).map((h, i) => React.cloneElement(h, { key: i }))} @@ -122,6 +123,7 @@ export class NextScript extends Component { return (