Next.js 入门超详解教程

简介

最近才学习了 React,现在要接触服务端渲染,趁热打铁把 Next 学一下,关于 Next.js,可以移步到知乎讨论:
知乎:关于 Next.js 的讨论

初识Next.js

安装

  1. Next.js 支持 Windows、Mac 和 Linux系统,均可安装,但是前提是你已经安装了 Node.js
  2. 创建示例项目的过程如下:
     mkdir hello-next
     cd hello-next
     npm init -y
     npm install --save react react-dom next
     mkdir pages

使用

  1. 打开 hello-next/package.json,替换 scripts
     "scripts": {
         "dev": "next",
         "build": "next build",
         "start": "next start"
     }
  2. 启动
     npm run dev
    在浏览器中打开 http://localhost:3000,你会看到页面显示 404 | This page could not be found.
  3. 创建你的第一个页面
    • 创建 pages/index.js,并输入:
        const Index = () => (
          <div>
            <p>Hello Next.js</p>
          </div>
        );
        export default Index;
    • 再次输入 npm run dev,就能看到效果了
    • 上述案例中,我们在 pages/index.js 模块中默认(default)导出了一个简单的 React 组件
  4. 试错
    • 尝试着错一次:将 pages/index.js 改为:
        const Index = () => (
          <div>
            <p>Hello Next.js
          </div>
        );
        export default Index;
    • 重新启动,浏览器显示:
    • 一般情况下,Next.js 将跟踪此类错误并在浏览器中显示,这便于我们快速发现错误,而你修改代码并保存后,页面将立即出现对应结果,而不会重新加载整个页面,这是通过 webpack 的 模块热替换 实现的,Next 默认支持这个功能

页面间导航

Introduction

  1. 我们的应用程序虽然很简单,只有一个页面,但是我们可以添加任意多个页面,例如:
    • 创建 pages/about.js 来新建 “About” 页面
        const About = () => (
          <div>
            <p>About Page</p>
          </div>
        );
        export default About;
    • 修改 pages/index.js
        const Index = () => (
          <div>
            <p>Hello Next.js</p>
            <a href="http://localhost:3000/about">This is a link to About-Page</a>
          </div>
        );
        export default Index;
    • 之后我们可以通过 http://localhost:3000/about 来访问该页面
    • 之后,我们需要连接两个页面,首先想到的是可以用一个 HTML 的 <a /> 标签实现,但是结果就是:浏览器会向服务器请求下一页并刷新当前页面,也就是这样做并不会执行客户端导航
  2. 为了支持浏览器端导航,我们需要使用 Next.js 提供的 Link 组件,这个组件是通过 next/link 导出的,接下来我们将使用它
  3. 我们需要准备一个简单的 Next.js 应用课程,请在 hello-next 下输入:
     git clone https://github.com/zeit/next-learn-demo.git
  4. 现在我们进入 hello-next/next-learn-demo/1-navigate-between-pages 启动程序:
     cd next-learn-demo/1-navigate-between-pages
     npm install
     npm run dev

使用Link组件

注意,接下来的操作均在 hello-next/next-learn-demo/1-navigate-between-pages 下完成的

  1. pages/index.js 中添加:
     import Link from 'next/link';
     export default function Index() {
       return (
         <div>
           <p>Hello next.js</p>
           <Link href="/about">
             <a>About Page</a>
           </Link>
         </div>
       )
     }
    • 在这里,我们将 next/link 导入为 Link,并按照如下的方式使用:
        <Link href="/about">
          <a>About Page</a>
        </Link>
    • 访问 3000 端口可查看结果
  2. 这次点击链接同样会导航到 “About” 页面,这是客户端导航,操作在浏览器中进行,而不向浏览器发送请求,你可以通过打开浏览器的 网络请求检查器(network request inspector) 来验证这一点
  3. 后退按钮:
    当你点击链接,再点击后退时,依然会切换到历史记录的上一页,也就是 next/link 为你完成了所有 location.history 的操作

添加链接道具

  1. 或许你需要在连接中添加属性或道具,比如你需要向链接中添加 title 属性,我们可以这样添加它:
     <Link href="/about">
         <a title="About-Pages">About Page</a>
     </Link>
    查看元素,可以看到结果如下:
  2. 切记不可添加到错误的地方去,若写成如下:
     <Link href="/about" title="About-Pages">
         <a>About Page</a>
     </Link>
    则会在控制台中报错:
  3. 实际上,Link 组件上的标题道具无效,是因为 Link 只是一个包装器组件,只接收 href 和一些类似的道具。如果需要向其添加道具,则需要将道具添加到其子项,这种情况下,Link 组件的子代是锚标记

使用共享组件

Introduction

  1. 我们可以通过导出 React 组件并将该组件放在 pages 目录中来创建页面,每个页面的 URL 都是基于文件名的,由于导出的页面是 JavaScript 模块,因此我们也可以将其他 JavaScript 组件导入其中
  2. 我们将创建一个公共的 Header 组件并将其用于多个页面,最后我们将研究实现 Layout 组件,并了解它如何帮我们定义多个页面的外观

运行

  1. 我们之前已经安装过了 next-learn-demo,这里直接使用:
    • 进入 hello-next/next-learn-demo/2-using-shared-components,之后我们的操作也会在此目录下
  2. 运行:
     npm install
     npm run dev

创建标题组件

  1. 下面创建一个 Header 组件,创建 2-using-shared-components/components/Header.js
     import Link from 'next/link';
     const linkStyle = {
       marginRight: 15,
     };
     const Header = () => (
       <div>
         <Link href="/">
           <a style={marginRight}>Home</a>
         </Link>
         <Link href="/about">
           <a style={marginRight}>About</a>
         </Link>
       </div>
     );
     export default Header;
  2. 现在,导入 Header 组件并在页面中使用它:
    • index.js 修改为:
        import Header from '../components/Header';
        export default function Index() {
          return (
            <div>
              <Header />
              <p>Hello Next.js</p>
            </div>
          )
        }
    • about.js 修改为:
        import Header from '../components/Header';
        export default function Index() {
          return (
            <div>
              <Header />
              <p>This is the About Page</p>
            </div>
          )
        }
    • 启动之后可以查看结果
  3. 试错:现在将 components 目录改名为 comps,报错如下:
    • 我们不需要将我们的组件放在一个特殊的目录里,也就是说,该组件目录名称可以取为任何,实际上,唯一特殊的目录是 /pages/public,你甚至可以在 /pages 里面创建组件.

布局组件

本节依然是在 2-using-shared-components/ 下完成的

  1. 我们将创建 Layout 组件,以实现各页面上的通用样式,在 components/MyLayout.js 中输入:
     import Header from './Header';
     const layoutStyle = {
       margin: 20,
       padding: 20,
       border: '1px solid #DDD'
     };
     const Layout = props => (
       <div style={layoutStyle}>
         <Header />
         {props.children}
       </div>
     );
     export default Layout;
  2. 完成操作后,我们可以在页面中使用以下布局:
    • pages/index.js 中输入:
        import Layout from '../components/MyLayout';
        export default function Index() {
          return (
            <Layout>
              <p>Hello Next.js</p>
            </Layout>
          )
        }
    • pages/about.js 中输入:
        import Layout from '../components/MyLayout';
        export default function About() {
          return (
            <Layout>
              <p>This is the about page</p>
            </Layout>
          )
        }
    • 启动,查看样式
  3. 试错:将 MyLayout.js 中的 {props.children} 删除,再启动,观察结果:
    • 页面上只保留了 Header 的内容,其他的均消失了

渲染子组件

  1. 前一个试错中,我们删除了 {props.children},则 Layout 无法呈现我们放入 Layout 元素内的内容,如下所示:
     export default function About() {
       return (
         <Layout>
           <p>This is the about page</p>
         </Layout>
       );
     }
    但这只是创建布局组件的一种方法,以下是其他方法。
  2. 方法一:布局为高阶组件
     // components/MyLayout.js
     import Header from './Header';
     const layoutStyle = {
       margin: 20,
       padding: 20,
       border: '1px solid #DDD'
     };
     const withLayout = Page => {
       return () => (
         <div style={layoutStyle}>
           <Header />
           <Page />
         </div>
       );
     };
     export default withLayout;
     // pages/index.js
     import withLayout from '../components/MyLayout';
     const Page = () => <p>Hello Next.js</p>;
     export default withLayout(Page);
     // pages/about.js
     import withLayout from '../components/MyLayout';
     const Page = () => <p>This is the about page</p>;
     export default withLayout(Page);
  3. 方法二:页面内容作为道具
     // components/MyLayout.js
     import Header from './Header';
     const layoutStyle = {
       margin: 20,
       padding: 20,
       border: '1px solid #DDD'
     };
     const Layout = props => (
       <div style={layoutStyle}>
         <Header />
         {props.content}
       </div>
     );
     export default Layout;
     // pages/index.js
     import Layout from '../components/MyLayout.js';
     const indexPageContent = <p>Hello Next.js</p>;
     export default function Index() {
       return <Layout content={indexPageContent} />;
     }
     // pages/about.js
     import Layout from '../components/MyLayout.js';
     const aboutPageContent = <p>This is the about page</p>;
     export default function About() {
       return <Layout content={aboutPageContent} />;
     }

创建动态页面

Introduction

  1. 之前通过使用组件,我们创建了包含了多个页面的小案例,之前为了创建一个页面,我们必须新建一个文件作为模块导出,但是在一个真正的应用程序中,我们还需动态地创建页面以显示动态内容,接下来我们会使用 查询字符串 来实现这一点
  2. 我们将创建一个简单的博客应用,它在主页上展示一个所有文章的列表,展示如下:
    • 主页有文章列表
    • 点击某标题的链接,会出现对应的文章

安装设置

  1. 我们仍然使用之前安装过的 next-learn-demo,进入 next-learn-demo/3-create-dynamic-pages,接下来的一切也将在这个目录下完成
  2. 运行
     npm install
     npm run dev

添加文章列表

  1. 首先,我们在文章主页添加标题列表,如下:
     import Link from 'next/link';
     import Layout from '../components/MyLayout.js';
     const PostLink = props => (
       <li>
         <Link href={`/post?title=${props.title}`}>
           <a>{props.title}</a>
         </Link>
       </li>
     );
     export default function Blog() {
       return (
         <Layout>
           <h1>My Blog</h1>
           <ul>
             <PostLink title="Hello Next.js" />
             <PostLink title="Learn Next.js is awesome" />
             <PostLink title="Deploy apps with Zeit" />
           </ul>
         </Layout>
       )
     }

通过查询字符串传递数据

  1. 我们将通过查询字符串作为参数(也称查询参数)并传递数据,如:
     // pages/index.js
     const PostLink = props => (
       <li>
         <Link href={`/post?title=${props.title}`}>
           <a>{props.title}</a>
         </Link>
       </li>
     );
    • 此例中,查询参数是 title,我们使用 PostLink 来执行的操作
    • 你也可以检查 Link 组件的 href 属性,以此类推,你可以使用查询字符串传递任何类型的数据

创建Post页面

  1. 现在我们需要创建 post 页面来显示博客文章,为此,我们需要从查询字符串中获得标题,
  2. 创建 pages/post.js 文件:
     import { useRouter } from 'next/router';
     import Layout from '../components/MyLayout';
     const Page = () => {
       const router = useRouter();
       return (
         <Layout>
           <h1>{router.query.title}</h1>
           <p>This is the blog post content.</p>
         </Layout>
       );
     };
     export default Page;
    • 启动项目,并点击三个标题链接
  3. 上面的运作过程如下:
    • 首先从 next/router 导入并使用 useRouter 函数,该函数返回 Next.js 的是 router 对象
    • 使用路由器(router)中的 query 对象,该对象保存了所有查询参数
    • 然后,使用 router.query.title 获取标题
  4. useRouter 函数的介绍:
    • useRouter 允许你访问页面中的 router 对象,它是一个 React Hook,能与功能组件协同合工作
    • 之前的示例中,useRouter 函数被放到预添加的页面组件中,而下面示例中,useRouter 函数在 Content 组件中,预添加的组件是 Page,但是功能不变
        import { useRouter } from 'next/router';
        import Layout from '../components/MyLayout';
        const Content = () => {
          const router = useRouter();
          return (
            <>
              <h1>{router.query.title}</h1>
              <p>This is the blog post content.</p>
            </>
          );
        };
        const Page = () => (
          <Layout>
            <Content />
          </Layout>
        );
        export default Page;

使用动态路由清理URL

Introduction

请确保你正在使用是 Next.js 9 或更高版本
接下来的操作都会在 next-learn-demo/4-clean-urls 中进行,请调至指定目录

  1. 我们已经知道了如何使用查询字符串创建动态页面,指向我们的某个博客文章的链接如:
     http:// localhost:3000 / post?title = Hello%20Next.js
    而此链接要表达的却是:
     http:// localhost:3000 / p / hello-nextjs

动态路由

  1. 启动项目,在 4-clean-urls/ 下输入:

     npm install
     npm run dev
  2. 我们将使用 Next.js 的 动态路由 功能,它允许你处理 /pages 动态路由

  3. 现在我们将创建新页面,并命名为 pages/p/[id].js,这也是我们创建的第一个动态路由,步骤如下:

    • 首先,在 /pages 内添加文件夹 /p
    • 然后,你需要在 /p 文件夹中创建 [id].js,并在这些 js 文件中添加如下内容:
        import { useRouter } from 'next/router';
        import Layout from '../../components/MyLayout';
        export default function Post() {
          const router = useRouter();
          return (
            <Layout>
              <h1>{router.query.id}</h1>
              <p>This is the blog post content.</p>
            </Layout>
          );
        };
  4. 前一页是特殊的,它不会处理 /about 等静态路由,而是会处理 p/ 之后的路由,例如,此页面将处理 /p/hello-next.js,而 /p/post-1/another

  5. 页面名称中的带有方括号([])使其成为动态路由,你不能使页面名称的一部分成为动态名称,而只能使全名成为动态名称,例如,支持 /pages/p/[id].js,但不支持 /pages/p/post-[id].js

  6. 创建动态路线时,我们在方括号([])之间添加了 id,这是页面接受到查询参数的名称,因此对于 /p/hello-nextjs,该 query 对象将具有 { id: 'hello-nextjs' },我们可以使用 useRouter() 进行访问

  7. 现在,我们新的动态路由添加多个链接,修改 pages/index.js

     // pages/index.js
     import Layout from '../components/MyLayout';
     import Link from 'next/link';
    
     const PostLink = props => (
       <li>
         <Link href="/p/[id]" as={`/p/${props.id}`}>
           <a>{props.id}</a>
         </Link>
       </li>
     )
     export default function Blog() {
       return (
         <Layout>
           <h1>My Blog</h1>
           <ul>
             <PostLink id="Hello-Next.js" />
             <PostLink id="Learn-Next.js" />
             <PostLink id="Deploy-Next.js" />
           </ul>
         </Layout>
       )
     }
    • 着重看看以下内容:
        const PostLink = props => (
          <li>
            <Link href="/p/[id]" as={`/p/${props.id}`}>
              <a>{props.id}</a>
            </Link>
          </li>
        )
      • <Link> 元素中,href 代表的是该页面在 pages 文件夹中的路径,而 as 代表的是该页面在浏览器中的 URL 路径
    • 现在,你可以重新启动项目,注意观察 URL 的变化!
  8. 动态路由可以很好地和浏览器历史记录配合使用,而我们要做的就是将 as 添加到链接组件中


为页面获取数据

Introduction

接下来的操作都会在 next-learn-demo/6-fetching-data 中进行,请调至指定目录

  1. 现在我们已经能创建一个相对完整的 Next.js 应用,但还没有解决的是:如何从远程数据源中获取数据?,Next.js 提供了一个标准 API 来获取页面所需的数据,即 getInitialProps 异步函数
  2. getInitialProps 只能添加到页面导出的默认组件中,在其他组件中是不会起作用的,它可以从远程数据源为指定页面获取数据,并将这些数据通过 props 传递到我们的页面,它会同时在客户端和服务器上工作,因为它在两个环境中都会被调用
  3. 我们将利用 getInitialProps 构建一个应用程序来显示有关 Batman TV Shows 的信息,利用的是公开的 TVmaze API
  4. 在即将演示的示例中,我们的主页上有一个文章列表,现在我们来展示 Batman TV shows 的节目列表,我们将从远程服务器上获取这些节目列表,而不是硬编码
    • 在这个示例中,我们使用的是 TVMaze API 来获取 TV shows 节目列表,这是一个搜索电视节目的 API

安装设置

  1. 进入 next-learn-demo/6-fetching-data,输入:
     npm install
     npm run dev
  2. 在浏览器中打开 http://localhost:3000/ 查看项目

获取 Batman Shows 的数据

  1. 首先,我们需要安装 isomorphic-unfetch,这是我们用来获取数据的工具库,这是浏览器的 fetch API 的一个简单实现,但在客户端和服务器环境中都可以使用
     npm install --save isomorphic-unfetch
  2. pages/index.js 替换为以下内容:
     // pages/index.js
     import Layout from '../components/MyLayout';
     import Link from 'next/link';
     import fetch from 'isomorphic-unfetch';
     const Index = props => (
       <Layout>
         <h1>Batman TV Shows</h1>
         <ul>
           {
             props.shows.map(show => (
               <li key={show.id}>
                 <Link href="/p/[id]" as={`/p/${show.id}`}>
                   <a>{show.name}</a>
                 </Link>
               </li>
             ))
           }
         </ul>
       </Layout>
     );
     Index.getInitialProps = async function() {
       const res = await fetch('https://api.tvmaze.com/search/shows?q=batman');
       const data = await res.json();
       console.log(`Show data fetched. Count: ${data.length}`);
       return {
         shows: data.map(entry => entry.show)
       };
     };
     export default Index;
  3. 我们着重分析下面这部分:
     Index.getInitialProps = async function() {
       const res = await fetch('https://api.tvmaze.com/search/shows?q=batman');
       const data = await res.json();
       console.log(`Show data fetched. Count: ${data.length}`);
       return {
         shows: data.map(entry => entry.show)
       };
     };
    • 这是一个静态异步函数,可以添加到程序的任何页面中,使用此函数,我们就可以获取数据并作为 props 传递给我们的页面
    • 以下便是我们的抓取结果,数据被抓取后,将会作为 props 的 ‘show’ 属性传递我们的页面中
  4. 注意,我们之前有一行用于打印信息的代码:
     console.log(`Show data fetched. Count: ${data.length}`);
    • 那么到底是在服务器端输出呢,还是在浏览器端的控制台输出呢,现在刷新一下浏览器,会发现之后服务端的控制台显示
    • 在这种情况下,消息只会在服务端输出,因为我们的页面是在服务端绘制的,所以,我们在服务端已经有了数据,没有必要在客户端再次获取这些数据

实现 Post 页面

  1. 现在让我们把 TV show 的详细信息添加到 post 中:将 pages/p/[id].js 替换为以下内容:
     // pages/p/[id].js
     import Layout from '../../components/MyLayout';
     import fetch from 'isomorphic-unfetch';
     const Post = props => (
       <Layout>
         <h1>{props.show.name}</h1>
         <p>{ props.show.summary.replace(/<[/]?[pb]>/g), '' }</p>
       </Layout>
     );
     Post.getInitialProps = async function(context) {
       const { id } = context.query;
       const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
       const show = await res.json();
       console.log(`Fetched show: ${show.name}`);
       return { show };
     };
     export default Post;
    • 注意该页的 getInitialProps
        Post.getInitialProps = async function(context) {
          const { id } = context.query;
          const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
          const show = await res.json();
          console.log(`Fetched show: ${show.name}`);
          return { show };
        };
    • 该函数的第一个参数是 context 对象,此对象包含一个 query 对象,我们用 context.query 来获取信息,即 id 对象,并使其在 TVMaze API 中获取电视节目数据
  2. 在这个 getInitialProps 函数中,我们添加了一个 console.log 来打印节目的标题,现在我们看看它将打印到哪里
    • 打开服务器和客户端的控制台,然后启动项目,访问 3000 端口
    • 单击第一个 Batman show 的标题
  3. 结果是:会在客户端的控制台输出
    • 与之前不同的是,我们这次只能在客户端看到消息,这是因为我们通过客户端导航到了 post 页面
    • 当我们单击链接时,由于该链接是被 Next.js 的 <Link> 组件包装过的组件,所以页面转换将在浏览器中进行,而不会想服务器发起请求
    • 但是,如果你直接访问的是 post 页面,而不是点击链接(例如,你直接访问 http://localhost:3000/p/975 ),消息会被打印在服务端,而不是客户端

为组件添加样式

Introduction

接下来的操作都会在 next-learn-demo/7-styling-components 中进行,请调至指定目录

  1. 对于 React,我们可以使用许多不同的技术来设置样式,这些技术可以分为两大类:
    • 传统的基于 CSS 文件的样式设计(包括 SASS、PostCSS 等)
    • CSS in JS
  2. 传统的基于 CSS 文件的样式设计(尤其是 SSR)需要考虑一堆的实际问题,因此我们在为 Mext.js 设置样式时避免使用这种方法,相反,我们会在 JS 中使用 CSS,你可以使用它来设置某单个组件的样式,而不是导入 CSS 文件
  3. 为此,我们需要认识一个新的框架:styled-jsx,这是Next.js 预装了一个 CSS in JS 框架,它允许你为组件编写熟悉的 CSS 规则,这些 CSS 规则对组件以外的任何内容(当然也包括子组件)都没有影响,也就是说,你的 CSS 规则是有作用域的

安装设置

  1. 进入 next-learn-demo/7-styling-components
  2. 输入
     npm install
     npm run dev
  3. 启动项目,访问应用

设置主页的样式

  1. 现在我们在主页中添加一些样式,进入 pages/index.js

     // pages/index.js
    
     import Layout from '../components/MyLayout';
     import Link from 'next/link';
    
     function getPosts() {
       return [
         { id: 'hello-nextjs', title: 'Hello Next.js' },
         { id: 'learn-nextjs', title: 'Learn Next.js is awesome' },
         { id: 'deploy-nextjs', title: 'Deploy apps with ZEIT' }
       ];
     }
    
     export default function Blog() {
       return (
         <Lauout>
           <h1>My Blog</h1>
           <ul>
             {
               getPosts().map(post => (
                 <li>
                   <Link href="/p/[id]" as={`/p/${post.id}`}>
                     <a>{post.title}</a>
                   </Link>
                 </li>
               ))
             }
           </ul>
           <style jsx>{`
             h1, a {
               font-family: 'Arial';
             }
             ul {
               padding: 0
             }
             li {
               list-style: none;
               margin: 5px, 0;
             }
             a {
               text-decoration: none;
               color: red;
             }
             a:hover {
               opacity: 0.6;
             }
           `}</style>
         </Lauout>
       )
     }
    • 那个 <style jsx> 元素,就是我们编写 CSS 规则的地方
    • 现在可以启动项目,查看结果
  2. 上面的代码中,我们没有直接在 <style> 标签内编写样式代码,而是写在模板字符串({``})里面的,Next.js 默认支持 babel 语法,而 styled-jsx 可以看做 babel 的一个插件,它将解析所有 CSS 并将其应用于构建工程

CSS样式和嵌套组件

  1. 现在我们将会对主页做一些更改,我们将会像这样隔离 Link 组件:

     import Layout from '../components/MyLayout';
     import Link from 'next/link';
    
     function getPosts() {
       return [
         { id: 'hello-nextjs', title: 'Hello Next.js' },
         { id: 'learn-nextjs', title: 'Learn Next.js is awesome' },
         { id: 'deploy-nextjs', title: 'Deploy apps with ZEIT' }
       ];
     }
    
     const PostLink = ({ post }) => (
       <li>
         <Link href="/p/[id]" as={`/p/${post.id}`}>
           <a>{post.title}</a>
         </Link>
       </li>
     );
    
     export default function Blog() {
       return (
         <Layout>
           <h1>My Blog</h1>
           <ul>
             {getPosts().map(post => (
               <PostLink key={post.id} post={post} />
             ))}
           </ul>
           <style jsx>{`
             h1, a {
               font-family: 'Arial';
             }
             ul {
               padding: 0;
             }
             li {
               list-style: none;
               margin: 5px 0;
             }
             a {
               text-decoration: none;
               color: blue;
             }
             a:hover {
               opacity: 0.6;
             }
           `}</style>
         </Layout>
       );
     }
    • 运行后发现:h1 的样式还在,但是链接已经失效了,如下:
  2. 上述结果显示:CSS 样式规则对子组件中的元素没有影响,styled-jsx 这个特性可以帮助你管理大型的应用程序的样式,在这种情况下,我们需要直接设置子组件的样式,在我们的示例中,我们需要对 Link 组件执行以下操作:

     const PostLink = ({ post }) => (
       <li>
         <Link href="/p/[id]" as={`/p/${post.id}`}>
           <a>{post.title}</a>
         </Link>
         <style jsx>{`
           li {
             list-style: none;
             margin: 5px 0;
           }
           a {
             text-decoration: none;
             color: blue;
             font-family: 'Arial';
           }
           a:hover {
             opacity: 0.6;
           }
         `}</style>
       </li>
     );

全局样式

  1. 有时我们确实需要更改子组件内部的样式,尤其是在使用支持 React 的 markdown 时

    • 我们需要安装 react-markdown
        npm install --save react-markdown
  2. 下面就是全局样式派上用场的地方,现在就来试试利用 styled-jsx 添加一些全局样式,打开 pages/p/[id].js

     import { useRouter } from 'next/router';
     import Markdown from 'react-markdown';
     import Layout from '../../components/MyLayout';
    
     export default () => {
       const router = useRouter();
       return (
         <Layout>
           <h1>{router.query.id}</h1>
           <div className="markdown">
             <Markdown
               source={`
     This is our blog post.
     Yes. We can have a [link](/link).
     And we can have a title as well.
    
     ### This is a titlr.
    
     And here's the content.
               `}
             />
           </div>
           <style jsx global>{`
             .markdown {
               font-family: 'Arial';
             }
             .markdown a {
               text-decoration: none;
               color: red;
             }
             .markdown a:hover {
               opacity: 0.6;
             }
             .markdown h3 {
               margin: 0;
               padding: 0;
               text-transform: uppercase;
             }
           `}</style>
         </Layout>
       );
     };
    • 注意,markdown 语法部分的缩进不可修改!!!
    • 我们定义的 style 作用于整个 <div> 标签部分,也就是作用于全局,虽然这样很方便,但是还是建议写带有作用域的样式
    • 尽管如此,这依然是一个比普通样式标签更好的解决方案,使用 styled-jsx 时,所有必要的特定于浏览器厂商前缀和 CSS 校验都通过了一个 Babel 插件完成了,这并不会导致额外的开销

部署 Next.js 应用程序

Introduction

接下来的操作都会在 next-learn-demo/8-deploying 中进行,请调至指定目录

  1. ZEIT Now 是将应用程序部署到生产环境的最简单和可扩展的方法,但是,你可以部署 Next.js 应用程序,而且它相对来讲比较简单
  2. 现在我们将部署 Next.js 应用程序
  3. 进入 next-learn-demo/8-deploying,输入命令:
     npm install
     npm run dev

部署到 ZEIT Now

  1. 我们来看一下 package.json 文件的 script 配置段:
     "scripts": {
        "dev": "next",
        "build": "next build",
        "start": "next start"
      },
  2. 首先,我们需要为生产环境编译我们的 Next.js 应用程序,它将生成一组优化的用于生产环境的代码:
     npm run build
  3. 然后,你需要启动 Next.js 应用程序并监听某个端口,此服务器将执行服务器端渲染并返回静态页面(使用上述命令编译)
     npm run start

运行两个实例

  1. 现在我们将为我们的应用程序启动两个实例,通常这样是为了横向扩展我们的应用程序
  2. 首先,将 package.json 中的 script 配置端替换为以下的内容:
     "scripts": {
       "dev": "next",
       "build": "next build",
       "start": "next start -p %PORT%"
     }
    • 我们更改了 start 脚本,它卸载接受一个代表端口号的参数来启动应用程序
    • 注意,%PORT% 是对于 Windows 来说的,Linux 对应的是 $PORT
  3. 现在构建我们的应用程序:
     npm run build
  4. 现在我们需要安装一个新的包,即:cross-env,我们在全局环境下安装:
     npm install cross-env -g
  5. 同时打开两个终端,分别在终端中输入:
     cross-env PORT=8000 npm start
     cross-env PORT=9000 npm start
    • 在 Linux 上,则直接打开两个命令行终端,并分别在每个命令行终端上运行:
        PORT=8000 npm start
        PORT=9000 npm start
  6. 现在,在浏览器分别查看:http://localhost:8000http://localhost:9000
  7. 可以得到的结果是,你只需要对应用程序构建一次。然后就可以在任意多个端口上启动它

结语

求进之路,持之谦卑,此教程是从官网理解翻译来的,增删了一些知识,我不在乎别人怎么评价,我已经进步了…

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 2
小李世界

稍微看了下,还没有弄 node 服务端吧

9个月前 评论
Ozzie (楼主) 9个月前
小李世界 (作者) 9个月前

@likunyan 加油 :muscle:

9个月前 评论
小李世界 9个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!