next.js & material-ui - 让他们工作
next.js & material-ui - getting them to work
我正在尝试 next.js,但我无法让最简单的设置正常工作。
这是我的设置:
相关库:
- "react": "^16.2.0",
- "react-dom": "^16.2.0",
- "next": "^4.2.2",
- "express": "^4.16.2",
- "next-routes": "^1.2.0",
- "material-ui": "^0.20.0",
server.js
const express = require('express')
const next = require('next');
const routes = require('./routes');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handler = routes.getRequestHandler(app, ({req, res, route, query}) => {
render(req, res, route.page, query);
});
const server = express();
app.prepare()
.then(() => {
server.use(handler).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
routes.js
const routes = module.exports = require('next-routes')();
routes
.add({name: 'walk', pattern: '/walk/:id'})
_document.js
import Document, { Head, Main, NextScript } from 'next/document';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
export default class extends Document {
static async getInitialProps(ctx) {
const props = await Document.getInitialProps(ctx);
const userAgent = ctx.req.headers['user-agent'];
return {
...props,
userAgent,
};
}
render() {
return (
<html>
<Head>
<title>ShareWalks</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link rel="stylesheet" href="https://sharewalks.com/shared.css" />
</Head>
<body>
<MuiThemeProvider muiTheme={getMuiTheme({ userAgent: this.props.userAgent })}>
<div>
<Main />
<NextScript />
</div>
</MuiThemeProvider>
</body>
</html>
);
}
}
pages/index.js(有效)
import React, {Component} from 'react';
import Head from 'next/head';
class App extends Component {
static async getInitialProps(args) {
return {};
}
render() {
return (
<div>
<Head>
<title>ShareWalks</title>
</Head>
<p>Yup</p>
</div>
);
}
}
export default App;
pages/walk.js(这里报错)
import React, {Component} from 'react';
import {Head} from 'next/head';
class Walk extends Component {
static async getInitialProps({query}) {
console.log('query: ', query);
return {id: query.id}; //added to props
}
render() {
return (
<div>
<Head>
<title>Walking</title>
</Head>
<p>{`Walk #${this.props.id}`}</p>
</div>
);
}
}
export default Walk;
当我转到 localhost:8080/walk/2 或 localhost:8080/walk?id=2 时出现错误。控制台确实按预期打印出 id,但是随后:
query: { id: '2' }
TypeError: Cannot read property 'toLowerCase' of undefined
at a.renderDOM (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:390)
at a.render (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:228)
at a.read (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:35:250)
at renderToString (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:44:6)
at renderPage (/home/terry/myProjects/PWA/sw-next/node_modules/next/dist/server/render.js:174:26)
at Function.getInitialProps (/home/terry/myProjects/PWA/sw-next/node_modules/next/dist/server/document.js:83:25)
at Function._callee$ (/home/terry/myProjects/PWA/sw-next/.next/dist/pages/_document.js:138:59)
at tryCatch (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:296:22)
at Generator.prototype.(anonymous function) [as next] (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:114:21)
第一个猜测是用你的服务器文件试试这个 - 最大的问题是你没有在路由本身上设置 id 参数,所以在客户端 - 它不知道寻找 ID。
试试这个:
const express = require('express')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
server.get('/walk/:id', (req, res) => {
return app.render(req, res, '/walk', { id: req.params.id })
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
好的,让 material-ui 与 Next.js 一起工作是为专业人士准备的.据我所知,你不能使用_document.js来设置material-ui。有人能告诉我为什么吗?反正我是这样写了一个高阶组件:
/components/hocs/withMui.js
import React, {Component} from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import injectTapEventPlugin from 'react-tap-event-plugin' //still needed?
import myTheme from 'styles/theme'
const muiTheme = myTheme;
export default function(NextPage) {
class outputComponent extends Component {
static async getInitialProps(ctx) {
const {req} = ctx;
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent;
let pageProps = {};
if (NextPage.getInitialProps) {
pageProps = await NextPage.getInitialProps(ctx);
}
return {
...pageProps,
userAgent
}
}
render() {
let userAgent = this.props.userAgent;
return (
<MuiThemeProvider muiTheme={getMuiTheme({userAgent, ...muiTheme})}>
<NextPage {...this.props} />
</MuiThemeProvider>
);
}
}
return outputComponent;
}
并将其用于任何页面:
/pages/index.js
import React, {Component} from 'react'
import withMui from 'components/hocs/withMui';
import RaisedButton from 'material-ui/RaisedButton'
import Dialog from 'material-ui/Dialog'
import FlatButton from 'material-ui/FlatButton'
const styles = {
container: {
textAlign: 'center',
paddingTop: 200
}
}
class Index extends Component {
static getInitialProps ({ req }) {
}
constructor (props, context) {
super(props, context)
this.state = {
open: false
}
}
handleRequestClose = () => {
this.setState({
open: false
})
}
handleTouchTap = () => {
this.setState({
open: true
})
}
render () {
const standardActions = (
<FlatButton
label='Ok'
primary={Boolean(true)}
onClick={this.handleRequestClose}
/>
)
return (
<div style={styles.container}>
<Dialog
open={this.state.open}
title='Super Secret Password'
actions={standardActions}
onRequestClose={this.handleRequestClose}
>
1-2-3-4-5
</Dialog>
<h1>Material-UI</h1>
<h2>example project</h2>
<RaisedButton
label='Super Secret Password'
secondary
onClick={this.handleTouchTap}
/>
</div>
)
}
}
export default withMui(Index); //<--- just wrap the page
我已经创建了 github 存储库,您可以将 nextjs 与 material UI 一起使用
https://github.com/hadnazzar/nextjs-materialui
描述了添加 material ui 包以及如何创建示例组件的教程 here
如果您在使用旧版本 material UI 时遇到闪烁问题,我建议您查看此 article
yarn add @material-ui/core
_document.js
/* eslint-disable react/jsx-filename-extension */
import React from 'react';
import Document, {
Html, Main, NextScript,
} from 'next/document';
import { ServerStyleSheets } from '@material-ui/core/styles';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () => originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
_app.js
import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import theme from '../src/theme';
export default function MyApp(props) {
const { Component, pageProps } = props;
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<React.Fragment>
<Head>
<title>My page</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</React.Fragment>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
};
我正在尝试 next.js,但我无法让最简单的设置正常工作。
这是我的设置:
相关库:
- "react": "^16.2.0",
- "react-dom": "^16.2.0",
- "next": "^4.2.2",
- "express": "^4.16.2",
- "next-routes": "^1.2.0",
- "material-ui": "^0.20.0",
server.js
const express = require('express')
const next = require('next');
const routes = require('./routes');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handler = routes.getRequestHandler(app, ({req, res, route, query}) => {
render(req, res, route.page, query);
});
const server = express();
app.prepare()
.then(() => {
server.use(handler).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
routes.js
const routes = module.exports = require('next-routes')();
routes
.add({name: 'walk', pattern: '/walk/:id'})
_document.js
import Document, { Head, Main, NextScript } from 'next/document';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
export default class extends Document {
static async getInitialProps(ctx) {
const props = await Document.getInitialProps(ctx);
const userAgent = ctx.req.headers['user-agent'];
return {
...props,
userAgent,
};
}
render() {
return (
<html>
<Head>
<title>ShareWalks</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link rel="stylesheet" href="https://sharewalks.com/shared.css" />
</Head>
<body>
<MuiThemeProvider muiTheme={getMuiTheme({ userAgent: this.props.userAgent })}>
<div>
<Main />
<NextScript />
</div>
</MuiThemeProvider>
</body>
</html>
);
}
}
pages/index.js(有效)
import React, {Component} from 'react';
import Head from 'next/head';
class App extends Component {
static async getInitialProps(args) {
return {};
}
render() {
return (
<div>
<Head>
<title>ShareWalks</title>
</Head>
<p>Yup</p>
</div>
);
}
}
export default App;
pages/walk.js(这里报错)
import React, {Component} from 'react';
import {Head} from 'next/head';
class Walk extends Component {
static async getInitialProps({query}) {
console.log('query: ', query);
return {id: query.id}; //added to props
}
render() {
return (
<div>
<Head>
<title>Walking</title>
</Head>
<p>{`Walk #${this.props.id}`}</p>
</div>
);
}
}
export default Walk;
当我转到 localhost:8080/walk/2 或 localhost:8080/walk?id=2 时出现错误。控制台确实按预期打印出 id,但是随后:
query: { id: '2' }
TypeError: Cannot read property 'toLowerCase' of undefined
at a.renderDOM (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:390)
at a.render (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:228)
at a.read (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:35:250)
at renderToString (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:44:6)
at renderPage (/home/terry/myProjects/PWA/sw-next/node_modules/next/dist/server/render.js:174:26)
at Function.getInitialProps (/home/terry/myProjects/PWA/sw-next/node_modules/next/dist/server/document.js:83:25)
at Function._callee$ (/home/terry/myProjects/PWA/sw-next/.next/dist/pages/_document.js:138:59)
at tryCatch (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:296:22)
at Generator.prototype.(anonymous function) [as next] (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:114:21)
第一个猜测是用你的服务器文件试试这个 - 最大的问题是你没有在路由本身上设置 id 参数,所以在客户端 - 它不知道寻找 ID。
试试这个:
const express = require('express')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
server.get('/walk/:id', (req, res) => {
return app.render(req, res, '/walk', { id: req.params.id })
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
好的,让 material-ui 与 Next.js 一起工作是为专业人士准备的.据我所知,你不能使用_document.js来设置material-ui。有人能告诉我为什么吗?反正我是这样写了一个高阶组件:
/components/hocs/withMui.js
import React, {Component} from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import injectTapEventPlugin from 'react-tap-event-plugin' //still needed?
import myTheme from 'styles/theme'
const muiTheme = myTheme;
export default function(NextPage) {
class outputComponent extends Component {
static async getInitialProps(ctx) {
const {req} = ctx;
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent;
let pageProps = {};
if (NextPage.getInitialProps) {
pageProps = await NextPage.getInitialProps(ctx);
}
return {
...pageProps,
userAgent
}
}
render() {
let userAgent = this.props.userAgent;
return (
<MuiThemeProvider muiTheme={getMuiTheme({userAgent, ...muiTheme})}>
<NextPage {...this.props} />
</MuiThemeProvider>
);
}
}
return outputComponent;
}
并将其用于任何页面:
/pages/index.js
import React, {Component} from 'react'
import withMui from 'components/hocs/withMui';
import RaisedButton from 'material-ui/RaisedButton'
import Dialog from 'material-ui/Dialog'
import FlatButton from 'material-ui/FlatButton'
const styles = {
container: {
textAlign: 'center',
paddingTop: 200
}
}
class Index extends Component {
static getInitialProps ({ req }) {
}
constructor (props, context) {
super(props, context)
this.state = {
open: false
}
}
handleRequestClose = () => {
this.setState({
open: false
})
}
handleTouchTap = () => {
this.setState({
open: true
})
}
render () {
const standardActions = (
<FlatButton
label='Ok'
primary={Boolean(true)}
onClick={this.handleRequestClose}
/>
)
return (
<div style={styles.container}>
<Dialog
open={this.state.open}
title='Super Secret Password'
actions={standardActions}
onRequestClose={this.handleRequestClose}
>
1-2-3-4-5
</Dialog>
<h1>Material-UI</h1>
<h2>example project</h2>
<RaisedButton
label='Super Secret Password'
secondary
onClick={this.handleTouchTap}
/>
</div>
)
}
}
export default withMui(Index); //<--- just wrap the page
我已经创建了 github 存储库,您可以将 nextjs 与 material UI 一起使用 https://github.com/hadnazzar/nextjs-materialui
描述了添加 material ui 包以及如何创建示例组件的教程 here
如果您在使用旧版本 material UI 时遇到闪烁问题,我建议您查看此 article
yarn add @material-ui/core
_document.js
/* eslint-disable react/jsx-filename-extension */
import React from 'react';
import Document, {
Html, Main, NextScript,
} from 'next/document';
import { ServerStyleSheets } from '@material-ui/core/styles';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () => originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
_app.js
import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import theme from '../src/theme';
export default function MyApp(props) {
const { Component, pageProps } = props;
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<React.Fragment>
<Head>
<title>My page</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</React.Fragment>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
};