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:
- Front-end components render as HTML strings, streams
- The loading of server and client resources is handled differently. (The first screen may not load all JS…)
- Server, client state data transfer
- Pack tool chain
- 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:
- 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)
- The first server rendering is extremely efficient and requires as little JS as possible.
- Later client rendering can make the best use of the data brought down by the server.
- Convenient Static Site Generation (SSG) support.
- Support the TypeScript
- …
In other words, make development more dynamic and flexible, and render more statically efficient.
Here’s an example:
- In CMS systems such as WordPress, dynamic requirements are easy to meet, but static cache optimization is difficult to achieve.
- 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 application
import
Way.
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/abc
And so on, but it won’t match/post/create
./pages/post/[...slug].js
–>/post/1/2
./post/a/b/c
And 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 request
getServerSideProps
methods - If the page through the browser side
Link
As the component navigates in, Next makes a request to the server and runs it on the servergetServerSideProps
Method, and return JSON to the browser.
👉getServerSideProps
Methods are mostly pre-9.3 upgradesgetInitialProps
methods
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
getStaticProps
andgetStaticPaths
(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.
- use
getStaticProps
Method returns the data required for the page during the build phase. - If the page is dynamically routed, use
getStaticPaths
Method 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:
getStaticPaths
Method returnfallback
Very useful: iffallback
isfalse
Access 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 them
fallback
fortrue
Next, 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?
- If the page content is truly dynamic (for example, the source database, and changes frequently), use
getServerSideProps
Methods SSR. - 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 above
getServerSideProps
orgetInitialProps
Method 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.