Skip to content

How It Works

Naofumi Kagami edited this page Sep 13, 2022 · 4 revisions

The Next.js server acts as a BFF (Backend For Frontend). The following describes what this means for each specific scenario.

A GET request for a page served by Next.js as SSR

Assumption

  1. The browser requests the page at http://localhost:3000/categories
  2. In your Next.js project directory, you have a file /pages/categories/index.js to render this page.

In this case, the following will happen.

  1. The browser will send a GET /categories request to the Next.js server.
  2. The Next.js server will be route the request to the file /pages/categories/index.js.
  3. The /pages/categories/index.js file for SSR contains a getServerSideProps() function. Inside getServerSideProps(), the Next.js server will send a GET http://web:3000/categories request to the Rails API server to retrieve information about the categories.
  4. The Rails API server will return a JSON response to the Next.js server.
  5. The Next.js server will generate server-side rendered HTML and send it to the browser.

A GET request for a page not served by Next.js

Assumption

  1. The browser requests the page at http://localhost:3000/categories
  2. In your Next.js project directory, you DO NOT have a file /pages/categories/index.js to render this page.

In this case, the following will happen.

  1. The browser will send a GET /categories request to the Next.js server.
  2. Since the Next.js server does not have a file at /pages/categories/index.js, Next.js cannot process this request.
  3. Due to the rewrites() statement in nextjs/next.config.js (see below), the Next.js server will delegate the request to the Rails server.
  4. The Rails server can handle the GET /categories request. It will generate an HTML response and send this to the Next.js server.
  5. The Next.js server will send the HTML response to the browser.

nextjs/next.config.js

  // https://nextjs.org/docs/migrating/incremental-adoption#rewrites
  async rewrites() {
    return {
      // After checking all Next.js pages (including dynamic routes)
      // and static files we proxy any other requests
      fallback: [
        {
          source: '/:path*',
          destination: `http://web:3000/:path*`,
        },
      ],
    }
  },

A non-GET request from the browser

Assumption

  1. The browser is currently displaying http://localhost/categories/1/edit. This is a React page.
  2. The user enters information into the form and presses the submit button.

In this case, the following will happen.

  1. The form on the current page has the following attributes: <form action="/api/categories/1" method="PATCH" onSubmit={handleSubmit}>
  2. The handleSubmit() function should look like the code below. The handleSubmit() function will hand over form processing to the submitForm() function.
  3. The handleSubmit() function will send a PATCH request with the form data to http://localhost:3000/categories/1 from the browser.
  4. The Next.js server will receive the PATCH /categories/1 request. Due to nextjs/middleware.js (see below), it will send the request to the Rails server.
  5. The Rails server will receive the PATCH /api/categories/1 request. It will update the database, and then it will send back a JSON response to the Next.js server.
  6. The Next.js server will send the response, as is, to the browser.
  7. The browser will receive the JSON response. If the response is successful, then the browser will run the success callback. If it is unsuccessful, it will run the failure callback.

nextjs/pages/categories/[cid]/edit.js

  const handleSubmit = async (event) => {
    event.preventDefault()

    submitForm({
      form: event.currentTarget,
      success: (response, jsonResult) => {
        router.push(`/categories/${jsonResult.id}`)
      },
      failure: (response, jsonResult) => {
        setErrors(jsonResult)
      }
    })
  }

nextjs/middleware.js

// middleware.js
import { NextResponse } from 'next/server'

// This function can be marked `async` if using `await` inside
export function middleware(request) {
  // Page Requests to the Next.js router (except '/api/*') are sent to
  // the matching file in '/pages/'. However, these files cannot handle
  // non-GET requests.
  //
  // Therefore, we send them directly to the API server before the Next.js router
  // handles them.
  if ( requestIsGet(request) ) { return }
  if ( requestPathStartsWithApi(request) ) { return }

  return NextResponse.rewrite('http://web:3000' + request.nextUrl.pathname
                                                + request.nextUrl.search)
}

function requestIsGet(request) {
  return (request.method.toUpperCase() == 'GET')
}

function requestPathStartsWithApi(request) {
  return (request.nextUrl.pathname.indexOf('/api/') != -1)
}

// See "Matching Paths" below to learn more
export const config = {
  matcher: '/:path*',
}

Important notes

  1. The form action is sent to the Next.js server (on http://localhost:3000) and not to the Rails server (on http://web:3000). The browser should never access the Rails server directly. The Rails server is only accessible during development, and should be inaccessible in production.

ERB page

I described the actions for a React page above. An ERB page behaves very similarly. The only difference is that an ERB page does not need a handleSubmit() function.