根据Going Serverless with Next.js, Firebase, and Now开始前端开发之旅

Going Serverless with Next.js, Firebase, and Now - Lee Robinson
Learn how to build a serverless application with Next.js, Firebase, Cloud Firestore, and easily deploy it with Now.

一、文章梳理 基于阅读和实践获得

The Server

To use Firebase, we need to create a Node.js API(we will add firebase to the local server whose package name called app in the source ,not android/ios/web app).  This will allow us to use firebase-admin to communicate with our Cloud Firestore database.

After generating a service account in Firebase(In firebase console setting), you can create a server like so:

const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const admin = require('firebase-admin');

const server = express();//nodejs web server

// here firebase use the webchannel transport protocol,cannot use the conventional http proxy 
const firebase = admin
    .initializeApp(
        {
            credential: admin.credential.cert(require('../.firebase/service-account.json')), // get from firebase console setting,also have another auth mode
            databaseURL: 'https://your-name-here.firebaseio.com'  // get from firebase console database page,where should select the realtime database to show the databaseURL  
        },
        'server'  //app name we create
    )
    .firestore(); // return firestore object in the end

firebase.settings({ // add this to make reliable communication to the firebase,but may be degrade performance. We can also disable ssl using the settings.
  experimentalForceLongPolling: true
});

server.use(cors()); //load the midleware function  cors() to process the req and res
server.use(bodyParser.json());
server.use((req, _res, next) => {
    req.firebaseServer = firebase; //the last custom midleware function,where add new var to the req object
    next();
});

module.exports = server; //commonjs export way

Then, you're able to define routes to communicate with your database.See  source app/routes/api/index.js.

try {
    const server = require('../../lib/server');

    server.get('/api/cities', (req, res) => {
        req.firebaseServer
            .collection('cities')//get this database collection ref
            .get()
            .then((snapshot) => {
                const cities = [];

                snapshot.forEach((doc) => {//traversal all documents
                    cities.push(doc.data());// push the document content to the cities array.
                });

                res.json({cities});
            })
            .catch((error) => {
                res.json({error});
            });
    });

    module.exports = server; //agian export this server to serve
} catch (error) {
    console.log('API Error', error);
}

That's it for the server infrastructure(Whether or not to start this server?!). From here, it's easy to create new routes and add functionality. You can view the completed example here.

Next 9 Update: The preferred way to handle this now is with API routes (e.g., /pages/api/cities.js). (With the Next API routes ,the new Zeit Now will no need to set route in now.json, and no need express server. After explore the initial process ,we will chang to the new features intruduced by the new version ! todo)

The Client

Since we already created a custom API (Nodejs express server,not using react and next.js), we can use Next's default server(to start the server  using 'next start',not Next.js's custom server features) as a client  to communicate with Firebase(late we will use Next's default server as a client). Let's configure the app to be serverless via next.config.js(see source www/next.config.js,here www is the package directory. So now we find the repo have app and www package or directory, we call the repo type  monorepo .To manage the repo dependency,we can use bash and yarn/npm,yarn with workspace,lerna).

module.exports = { //commonjs export way
    target: 'serverless' //One Next.js build target,enforces additional constraints to keep us in the pit of success,can deploy to now platform,cannot use the Custom Server API. Next.js default build target is server,compatible with both next start and custom server setups,see https://nextjs.org/docs/api-reference/next.config.js/build-target
};

While the majority of the requirements were centered around creating basic React components(for Nextjs development), there's one part which requires more explanation: dynamic routing. For the city pages, we needed to be able to pass a city name as a route (e.g. /city/des-moines) and fetch data from our custom API.

The magic happens in our now.json file, which defines how the apps will be deployed(Now also have the route feature!). We can route all requests to /api/ to our Node custom API:

{
    "src": "/api/(.*)",
    "dest": "/app/routes/api" //route to Next server then to Node express server ?? How?
}

and all requests to /city/city-name/ to forward a query param along to a City component.

{
    "src": "/city/(?<name>[^/]+)$",// (?<name>[^/]+) is named capature group
    "dest": "/www/city?name=$name" //route to client(source in www dir,Next.js server, how forward to City component?)
}

Finally, we can create /pages/city.js(What use the pages dir in the Next app?) inside our Next app to fetch the query param for the city name and call our Node API(Http Request will first hit the Next app).

import {useEffect, useState} from 'react';// Next framework is based react ,and next app will use react to impl.
                                          // useEffect,use State,one for react function compoment life-time cycle management, one for react function comopment state manage,avoding using class compment.
import fetch from 'isomorphic-unfetch';// for both server and client req

function City() {
    const [city, setCity] = useState(null);//city as current state,setCity to set state

    useEffect(() => {
        async function fetchData() {
            const cityName = window.location.pathname.split('/').pop();//pop return last
            const baseUrl =
                process.env.NODE_ENV === 'production' ? `https://${window.location.host}` : 'http://localhost:3000';
            const res = await fetch(`${baseUrl}/api/city/${cityName}`);// get from Node server,we have not impl
            const data = await res.json();

            setCity(data);
        }//end async

        fetchData();
    }, []);//[] mean?

    return city ? city.name : 'Loading...';
}// end City compnent

export default City;// ES 6 default export. NEXT will import it ,built router to www/city( case-insensitive?)    how /www/city?name=beijing to this City?

Next 9 Update: The preferred way to handle this now is with the bracket notation (e.g., /pages/[city].js)(This is the Next router feature, so we need not previous solution: the  Zeit Now route feature and Next primary router feature.)

This gives you the flexibility to add a loading state or placeholder to improve your user experience.

二、一些知识点Import notes

Zeit Now route feature

  • routers(old feature)
    By default, routing is defined by the filesystem of your deployment. For example, if a user makes a request to /123.png, and your now.json file does not contain any routes with a valid src matching that path, it will fallback to the filesystem and serve /123.png if it exists.
  • redirects, rewrites, headers, cleanUrls, and trailingSlash(new feature)

Zeit Serverless functions

To create a Serverless Function, create a file in an /api directory from your project root with the appropriate language extension.If you are using Next.js, use the /pages/api directory instead.
When a user visits /api/get-user, they will receive the JSON from above. Note that you don't have to explicitly set up any routing rules for this to work. By default, filesystem routing is leveraged.
Path Segments Support in Now vs the Nextjs square brackets routes.

Now deploy error

   warning Error running install script for optional dependency:   "/zeit/337d1723/app/node_modules/grpc: Command failed
   cc1plus: error:     unrecognized command line option \"-Wno-cast-function-type\" [-Werror]

Error log show in the now console,or now logs https:/xxx.
Update firebase version to new.

--- a/app/package.json
+++ b/app/package.json
@@ -8,13 +8,17 @@
   },
   "license": "UNLICENSED",
   "dependencies": {
-        "@firebase/app-types": "0.3.10",
-        "@firebase/util": "0.2.14",
+        "@firebase/app-types": "0.5.1",
+        "@firebase/util": "0.2.40",
       "body-parser": "1.18.3",
       "cors": "^2.8.5",
       "express": "4.16.4",
-        "firebase": "5.9.3",
-        "firebase-admin": "7.2.0"
+        "firebase": "^7.6.2",
+        "firebase-admin": "^8.9.0",
+       "tunnel2": "0.0.4",
+       "firebase-admin-proxy": "4.2.1",
+       "caw": "2.0.1",
+       "https": "1.0.0"
   },