Skip to main content

6 Design Techniques Learned from Next.js

Free2021-01-02#Front-End#Mind#Nextjs#create serverless react app#下一代前端框架#前端API设计#前端一体化应用框架

This article, as the third (and final) piece in the Next.js series, records the design techniques I discovered from it, including API design, documentation design, framework design, etc., sharing them with you

Preface

Recently while researching SSR, I also gained more understanding of Next.js:

  • Comprehensive introduction: Next.js

  • Core features: [Next.js Hybrid Rendering](/articles/next-js 混合渲染/)

  • Design techniques: This article

This article, as the third (and final) piece in the Next.js series, records the design techniques I discovered from it, including API design, documentation design, framework design, etc., sharing them with you

Defining Base Classes May Not Be as Good as Defining Modules

First, both Classes and Modules are optional ways to organize code, and in the context of API design, both can be used to constrain writing styles and expose framework capabilities. Before the Module concept became orthodox, most frontend frameworks provided base classes to meet this need, because there was no choice

Typically, React exposes various lifecycle Hooks through the React.Component base class, while also defining component writing style:

// Components
class Clock extends React.Component {
  // Props
  constructor(props) {
    super(props);
    // State
    this.state = {date: new Date()};
  }

  // Lifecycle
  componentDidMount() { }
  componentWillUnmount() { }

  render() {
    // Template
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Integrating Props, State, Lifecycle, Template and other framework capabilities into a Class, called a Component. And, for a very long time, the only thing that could be called a Component in React was Class

How long is this very long time?

From React's birth until React Hooks was launched and evolved into complete form. Currently (2021/1/2) React Hooks is still not in complete form, features like componentDidCatch, getSnapshotBeforeUpdate, getDerivedStateFromError are not yet complete, see Do Hooks cover all use cases for classes? for details

That is to say, to this day, React Components is still equivalent to Class Components, early functional components could only be called Stateless Components, functional components after receiving Hooks support, although freed from Stateless, still have a small gap from complete form Class Components

Tightly binding the Components concept with Class is really a terrible choice, the highly anticipated Hooks fully illustrates this point. But Props, State, Lifecycle, Template these framework capabilities still need something to carry them, so, what's a better choice?

Probably Module. Emphasizing probably, because only in the aspect of organizing code, Module is purer than Class. Module only organizes code, grouping variables, functions and other syntax elements together, unlike Class which imposes additional concepts like instance state, member methods, etc.

For example, Next.js's Page definition is just a file module:

// pages/about.js
function About() {
  return <div>About</div>
}

export default About

The simplest Page, just needs to default export a React component. Need to use more features, then expose more established APIs on demand:

// pages/blog.js
function Blog({ posts }) {
  // Render posts...
}

// API 1
export async function getStaticProps() { }
// API 2
export async function getStaticPaths() { }
// API 3
export async function getServerSideProps() { }
// API n
export async function xxx() { }

export default Blog

Compared with Class-form API design, this Module-style API design is purer, doesn't impose additional syntax elements (especially Class such a massive foundation syntax element, bringing a bunch of super(), bind(this), static), in certain scenarios may be a better choice

File Convention Routing

There's no Router.register, no new Route(), no app.use() in Next.js, no routing definition API you can think of

Because there's simply no API, routing uses file path convention:

// Static routing
pages/index.js → /
pages/blog/index.js → /blog
pages/blog/first-post.js → /blog/first-post
pages/dashboard/settings/username.js → /dashboard/settings/username

// Dynamic routing
pages/blog/[slug].js → /blog/:slug (/blog/hello-world)
pages/[username]/settings.js → /:username/settings (/foo/settings)
pages/post/[...all].js → /post/* (/post/2020/id/title)

That is to say, identifying routes through the file path where source code is located, and even supporting wildcards, so magical, of course need to see the source code directory with own eyes to feel the visual impact:

pages
├── _app.js
├── _document.tsx
├── api
│?? ├── collection
│?? │?? ├── [id].tsx
│?? │?? └── index.tsx
│?? ├── photo
│?? │?? ├── [id].tsx
│?? │?? ├── download
│?? │?? │?? └── [id].tsx
│?? │?? └── index.tsx
│?? ├── stats
│?? │?? └── index.tsx
│?? └── user
│??     └── index.tsx
├── collection
│?? └── [slug].tsx
└── index.tsx

Seamless Linkage Between APIs

Through the first two articles, we know Next.js's problem to solve is pre-rendering, explored SSG, SSR two rendering modes around pre-rendering, and on this basis supports mixing different rendering modes including CSR:

  • ISR (Incremental Static Regeneration): Incremental static regeneration, periodically regenerate static HTML at runtime
  • SSG degraded SSR: When missing pre-generated static HTML, immediately perform SSR
  • SSR with static cache: After SSR completes, cache the result, next time hit static cache return directly (equivalent to SSG)
  • SSG combined with CSR: Generate static parts at compile time (page frame), CSR fills dynamic parts (page content)
  • SSR linked with CSR: URL direct access goes to faster SSR, SPA navigation goes to better experience CSR

From API design perspective at first glance, seems need to give each combination a distinctive name, and expose dedicated API, like SSGwithFallback, SSRwithStaticCache, PartialSSG, SPAMode...

However, Next.js not only supports all these mixing features, but didn't add any top-level API, its approach is adding some options, for example:

// SSG base version
export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

// SSG transforms to ISR, add revalidate property to return value
export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props

    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every second
    revalidate: 1, // In seconds
  }
}

// SSG advanced version aware of routing, implemented getStaticPaths
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // See the "paths" section below
    ],
    fallback: false
  };
}

// SSG transforms to SSR with static cache, change fallback option to true
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // See the "paths" section below
    ],
    fallback: true
  };
}

// SSG transforms to SSG degraded SSR, change fallback option to 'blocking'
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // See the "paths" section below
    ],
    fallback: 'blocking'
  };
}

This API linkage based on refined options is lighter to use, always brings users progressive feeling, don't need to understand all APIs, related design concepts at once, distinguish which category my scenario belongs to from top level, which API to use, but as scenario deepens, discover that most suitable API/option is right there

Can obviously feel this difference from documentation, for example, Next.js introduces ISR by guiding users to associated SSR with static cache mode:

Incremental Static Regeneration

With getStaticProps you don't have to stop relying on dynamic content, as static content can also be dynamic. Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in.

This works perfectly with fallback: true. Because now you can have a list of posts that's always up to date with the latest posts, and have a blog post page that generates blog posts on-demand, no matter how many posts you add or update.

Points, Interactive Beginner Tutorial

This point counts as documentation design technique (documentation, of course, also needs design), have seen many official documents/tutorials, only 3 left deep impression:

  • Redux Documentation: Story-style documentation, hand-in-hand designing redux bit by bit, can't stop reading

  • Electron Demo App: Interactive documentation, accurately speaking Demo with complete documentation, understand related feature usage while experiencing Demo App, is a lazier method than React Learn by Doing

  • Next.js Tutorial: Points, interactive beginner tutorial, finish dozens of pages of tutorial in one breath

P.S. Redux documentation refers to 2017 version, now seems changed many versions, reading is very poor (how can so few concepts produce so many documents)

How powerful are points, interactive beginner tutorials?

Let me persist through all tutorial content in a state of being so sleepy I'm confused, answer all test questions correctly, accumulate full 500 points (of course, don't fantasize, all correct has no reward whatsoever), recalling afterwards also feels incredible, techniques therein lie in:

  • Tutorial separated from documentation: Navigation bar first-level menu clearly distinguishes Docs and Learn, some concepts in tutorial have links to documentation, but can completely follow without reading all

  • Points: Tutorial prominent position top displays points earned, add points for each click

  • Interaction: Key chapters have test questions, answering correctly also adds points, total points can share to social platform (Twitter)

Seeing it this way, integrating small amount of mature online education models into documentation, may have excellent effect

Default Provision of Best Practices

Read Experiencing Technology and Good Products, deeply impressed by default works well proposed by Yubo therein, and Next.js counts as a real case of default works well in framework design

For example:

From production activity perspective, best practices should inherently be default provided, continuously sinking newly emerged best practices to environment layer, like npm package, ES Module, Babel, etc., today's frontend developers already almost don't need to care about these former best practices

Only from framework design perspective, default works well requires going further on basis of providing best practices, need to make best practices disappear, let users lazily think everything should naturally be so. Therefore, best practice is just a temporary state, parts not yet formed into best practice are what developers need to care about, and reflect differentiated competitiveness, once formed widely recognized best practice, should settle into default infrastructure, developers can obtain various benefits brought by these best practices without caring

From not yet formed best practice, to providing best practice, to default providing best practice, these 3 stages can be understood through an image lazy loading example:

// First stage: Not yet formed best practice
scroll
IntersectionObserver
// Businesses implement separately, no usage examples exist

// Second stage: Providing best practice
React Lazy Load Component
// Usage example
<LazyLoad height={683} offsetTop={200}>
  <img src='http://apod.nasa.gov/apod/image/1502/2015_02_20_conj_bourque1024.jpg' />
</LazyLoad>

// Third stage: Default providing best practice
next/image
// Usage example
<Image
  src="/me.png"
  alt="Picture of the author"
  layout="fill"
/>

Difference between third stage and second stage is, developers don't need to care which component can provide lazy loading function (choose best practice), directly use most ordinary Image component in component library, should-have functions naturally exist, and lazy loading is just one of them

Extending to Serverless

Under Serverless Wave, frontend ecosystem is also undergoing some changes, emerging various integrated applications:

  • Integrated applications with frontend project/backend project as main body: Like Midway Serverless, supports integrating React, Vue and other frontend projects

  • Integrated applications with SSR as main body: Like Next.js, supports deploying SSR and data interfaces (API endpoints) as Serverless Functions

Next.js provides SSR support, originally needing server environment, Serverless rise nicely solved SSR rendering service operations problem, therefore, its Vercel platform defaults to supporting deploying SSR services and APIs as Serverless Functions:

Pages that use Server-Side Rendering and API routes will automatically become isolated Serverless Functions. This allows page rendering and API requests to scale infinitely.

Although integrated applications like these haven't formed best practices, traditional frontend frameworks are undergoing transformation. Perhaps, on some future day, what replaces them is integrated application framework fully fused with Serverless technology, Universal system prevailing is also possible

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment