Recently implemented a blog system using next.js + editor.js. React is very smooth to develop. If you like React and need isomorphism, you can start with this article.

This article is from Hipo Log

If your app needs to be compatible with Internet explorer 9,10 and other browsers, go out and turn left to look for “traditional” methods like ejs

Why do WE need front isomorphism?

  • Search engine SEO and the first screen experience, need to render the page server
  • Increasingly rich front-end interactions require more powerful front-end frameworks.

Front-end isomorphism is a one-stop solution to the above problems: let a set of JavaScript code run on both the server side and the client side.

Why a modern front-end isomorphic framework?

Modern front-end frameworks have server-side rendering apis, so why do we need a homogeneous framework?

The reason is that for a normal isomorphic requirement, we need:

  1. Front-end components render as HTML strings, streams
  2. The loading of server and client resources is handled differently. (The first screen may not load all JS…)
  3. Server, client state data transfer
  4. Pack tool chain
  5. Performance optimization

The React SSR API has only four functions: RenderToString (), renderToStaticMarkup(), renderToNodeStream(), and renderToStaticNodeStream() (Vue is similar) can only meet the first requirement, we need more, The front-end isomorphic framework represented by Next. Js can not only meet the above basic requirements, but also bring us:

  1. Excellent development experience, do the same as development SPA, (yes this is the first important, otherwise it is better to choose traditional template rendering scheme)
  2. The first server rendering is extremely efficient and requires as little JS as possible.
  3. Later client rendering can make the best use of the data brought down by the server.
  4. Convenient Static Site Generation (SSG) support.
  5. Support the TypeScript

In other words, make development more dynamic and flexible, and render more statically efficient.

Here’s an example:

  1. In CMS systems such as WordPress, dynamic requirements are easy to meet, but static cache optimization is difficult to achieve.
  2. Hexo, for example, renders the page completely static (landing as a file), but the need to be a little more dynamic is almost impossible.

Next. Js is the leading-edge framework in front-end isomorphism, relying on the React rendering component. Of course Vue has nuxt.js, Angular has Angular Universal, and even Svelte has Sapper.

Before you start, the official documentation for Next. Js is highly recommended. It is very clear and easy to understand.

Getting Started | Next.js

Next. Js official Blog, also very recommended, each version of the update detailed and timely, exemplary.

Blog | Next.js


Next. Js concise tutorial

This article is based on Next. Js 9.3. There is no principle involved here, just a guide for getting started.

Routing based on file path

page

A typical front-end Web application can be simplified into two parts: a routing page and an API. Next’s routing system is automatically mapped based on file paths and does not require neutral configuration.

It is generally agreed to be in the root directory pages folder:

  • ./pages/index.tsx— > home page/
  • ./pages/admin/index.tsx –> /admin
  • ./pages/admin/post.tsx –> /admin/post
import styles from './style.module.css'
function About() {
  return <div>About</div>
}

export default About
Copy the code

The React component is exported by default, and Next will generate the routing page for you by default.

  • You don’t have to worry about how resources are configured to load in head
  • You can use CSS-in-JS, CSS Module, less, SASS, and so on, just like a SPA applicationimportWay.
Navigation between pages
import Link from 'next/link'
function Home() {
  return (
    <ul>
      <li>
        <Link href="/about">
          <a>About Us</a>
        </Link>
      </li>
    </ul>)}export default Home
Copy the code

Note that it is best to wrap the A element separately in Link.

To increase the Head
import Head from 'next/head'
function About() {
  return (
    <div>
      <Head>
        <title>Hipo Log - {props.post? .name ?? ''}</title>
      </Head>
      content
    </div>
  );
}
export default About
  
Copy the code
Dynamic import code split

Next also supports ES2020’s Dynamic Import () syntax, which can be used to split code or to elaborate server-side rendering when some third-party components rely on browser apis (SSR: false)

import dynamic from 'next/dynamic' const DynamicComponentWithCustomLoading = dynamic( () => import('.. /components/hello'), { loading: () => <p>... </p>, ssr: false } ) function Home() { return ( <div> <Header /> <DynamicComponentWithCustomLoading /> <p>HOME PAGE is here! </p> </div> ) } export default HomeCopy the code

👉 Note: be careful in page code import code!!

The more hooks are introduced, the more JS will be loaded after the online access, especially the following hook functions should be careful not to introduce extra code

API

The API type routing convention is in the./pages/ API folder, and next is automatically mapped to the API of the/API /* path

import { NextApiRequest, NextApiResponse } from 'next'

export default (req: NextApiRequest, res: NextApiResponse) => {
  res.status(200).json({ name: 'John Doe'})}Copy the code

The request method is retrieved from the REQ.

This way you can easily generate an API.

Dynamic routing

Normal applications have dynamic routing, which next cleverly supports by naming files.

  • ./pages/post/create.js –> /post/create
  • ./pages/post/[pid].js –> /post/1 , /post/abcAnd so on, but it won’t match/post/create
  • ./pages/post/[...slug].js –> /post/1/2./post/a/b/cAnd so on, but it won’t match/post/create , /post/abc

Query ({pid}, {slug: [‘a’, ‘b’]}). Router hook

import { useRouter } from 'next/router';

function About() {
  const router = useRouter();
  const { bID, pID } = router.query
  return <div>About</div>
}
Copy the code

Page SSR hooks and SSG

Most of the application content is not purely static, we need data query to render that page, and this needs to be satisfied by the homogeneous hook functions, with these hook functions, we can make a great experience of the web application under different requirements.

getServerSideProps(SSR) Request data on each visit

Export an async getServerSideProps method, which next calls on the server side on each request.

  • Methods are only run on the server, one side for each requestgetServerSidePropsmethods
  • If the page through the browser sideLinkAs the component navigates in, Next makes a request to the server and runs it on the servergetServerSidePropsMethod, and return JSON to the browser.

👉getServerSidePropsMethods are mostly pre-9.3 upgradesgetInitialPropsmethods

A major drawback of the pre-9.3 getInitialProps method is that the req and RES objects will be undefined in the browser. The page that uses it, if it’s a browser rendering you need to explicitly request it again within the component. The development experience was not great. If there are no special problems, it is recommended to use getServerSideProps instead of the getInitialProps method. Example:

import { GetServerSideProps, NextPage } from 'next'

interface PostProps {
  list: Post[]
}

const App: NextPage<PostProps> = props => {
  return <div></div>
}

export const getServerSideProps: GetServerSideProps<PostProps> = async context => {
  const list = await context.req.service.post.getPost(context.params.postID)
  return {
    props: {
      list
    }
  }
}
export default App
Copy the code

getStaticPropsandgetStaticPaths(SSG) Request data at build time

SSG stands for static site generation, and things like Hexo or GatsbyJS build pages into static HTML files at build stage, allowing direct online access to HTML files for high performance.

In 9.0, next.js introduced automatic static optimization, which means that if a page doesn’t use the getServerSideProps and getInitialProps methods, Next generates HTML during the build phase to improve performance.

However, as mentioned above, application pages require dynamic content, so automatic static optimization is very limited.

Next went a step further in 9.3, introducing the getStaticProps and getStaticPaths methods to let developers specify which pages can be optimized for SSG.

  • usegetStaticPropsMethod returns the data required for the page during the build phase.
  • If the page is dynamically routed, usegetStaticPathsMethod to return all the route parameters and whether a fallback mechanism is required.
export async function getStaticPaths() { // Call an external API endpoint to get posts const res = await fetch('https://... /posts') const posts = await res.json() // Get the paths we want to pre-render based on posts const paths = posts.map(post => ({ params: { id: post.id }, })) // We'll pre-render only these paths at build time. // { fallback: false } means other routes should 404. return { paths, fallback: true }; } export const getStaticProps: GetStaticProps<InitProps> = async ({ params }) => { const data = await fetch( `http://... /api/p/${params.bookUUID }/${ params.postUUID }` ); return { props: { post: data, }, }; };Copy the code

Very simple to use, note that:

  • getStaticPathsMethod returnfallbackVery useful: iffallbackisfalseAccess to the method does not return a route 404
  • However, if you don’t want to get route parameters during build phase, you can set themfallbackfortrueNext, when accessing a dynamic route that is not in the build, the browser loading, and then the server starts to build the page, and then returns to the browser rendering, the cache will take effect when accessing the route again, very powerful!!
  • Static caches cannot be updated flexibly at present! For example, if blog content changes after build or fallback takes effect, there is no way to easily replace the cache.

How to choose SSR or SSG?

  1. If the page content is truly dynamic (for example, the source database, and changes frequently), usegetServerSidePropsMethods SSR.
  2. SSG can be used if the page is static or pseudo-dynamic (for example, a source database, but does not change).

That’s the main part of next.js, but here are some of the customizations that might be used.

Custom App

Use./pages/_app.tsx to customize the App, configure global CSS, or the getServerSideProps method to add data to each page.

function MyApp({ Component, pageProps }) {
  return <Component {. pageProps} / >
}

export default MyApp
Copy the code

The custom Document

You can use./pages/_document. TSX to customize the Document of the page, configure the page HTML, head properties, or use the renderPage method in the static getInitialProps method to include the entire React application.

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>)}}export default MyDocument
Copy the code

` < Html > `, ` < Head / > `, ` < Main / > ` and ` < NextScript / > ` is a must.

  • Used in app and Document abovegetServerSidePropsorgetInitialPropsMethod prevents the entire application from being automatically static optimized
  • The above app and Document do not execute in the browser, including the React hooks or lifecycle functions.

Custom build

The root directory uses next.config.js to configure webpack, which can be used to support less compilation, load on demand, path alias, etc.

Here is the configuration in the Hipo Log that supports Antd Design loading on demand.

const withLess = require('@zeit/next-less');
const withCss = require('@zeit/next-css');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const fixMiniCss = (nextConfig = {}) = > {
  return Object.assign({}, nextConfig, {
    webpack(_config, options) {
      // force react-scripts to use newer version of `mini-css-extract-plugin` and ignore css ordering warnings
      // (related to issue: https://github.com/facok/create-react-app/issues/5372)
      let config = _config;
      if (typeof nextConfig.webpack === 'function') {
        config = nextConfig.webpack(_config, options);
      }
      let miniCssExtractOptions = {};
      for (let i = 0; i < config.plugins.length; i++) {
        const p = config.plugins[i];
        if(!!!!! p.constructor && p.constructor.name === MiniCssExtractPlugin.name ) { miniCssExtractOptions = { ... p.options,ignoreOrder: true };
          // config.plugins[i] = new MiniCssExtractPlugin(miniCssExtractOptions);
          break; }}// config.plugins.push(new MiniCssExtractPlugin(miniCssExtractOptions));config.resolve.alias = { ... config.resolve.alias,'@pages': path.join(__dirname, './pages'),
        '@components': path.join(__dirname, './components'),
        '@entity': path.join(__dirname, './dbEntity'),
        '@services': path.join(__dirname, './services'),
        '@lib': path.join(__dirname, './lib'),
        '@util': path.join(__dirname, './services/util'),
        '@type': path.join(__dirname, './app.type'),};returnconfig; }}); };module.exports = fixMiniCss(
  withLess({
    cssModules: true. withCss({ webpack(config, options) {const { dev, isServer } = options;
        if (isServer) {
          const antStyles = /antd\/.*? \/style\/css.*? /;
          const origExternals = [...config.externals];
          config.externals = [
            (context, request, callback) = > {
              if (request.match(antStyles)) return callback();
              if (typeof origExternals[0= = ='function') {
                origExternals[0](context, request, callback);
              } else{ callback(); }},... (typeof origExternals[0= = ='function' ? [] : origExternals),
          ];

          config.module.rules.unshift({
            test: antStyles,
            use: 'null-loader'}); }returnconfig; }})}));Copy the code

Custom service

Next also supports Node startup to work with other frameworks for more complex server-side functionality, such as Hipo Log using it to bind databases such as Typeorm.

/ server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

constdev = process.env.NODE_ENV ! = ='production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then((a)= > {
  createServer((req, res) = > {
    // Be sure to pass `true` as the second argument to `url.parse`.
    // This tells it to parse the query portion of the URL.
    const parsedUrl = parse(req.url, true)
    const { pathname, query } = parsedUrl

    if (pathname === '/a') {
      app.render(req, res, '/b', query)
    } else if (pathname === '/b') {
      app.render(req, res, '/a', query)
    } else {
      handle(req, res, parsedUrl)
    }
  }).listen(3000, err => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')})})Copy the code

👉 Note: Do not or carefully customize if necessary. Otherwise, some of the magic of next.js might suffer.