Static Site Generation with Next.js and TypeScript - Project Overview

Many of today's most popular web applications, such as G-Mail and Netflix, are single-page applications (SPAs). Single-page applications deliver highly engaging and exceptional user experiences by dynamically rendering content without fully reloading whole pages. However, because single-page applications generate content via client-side rendering, the content might not be completely rendered by the time a search engine (or bot) finishes crawling and indexing the page. When it reaches your application, a search engine will read the empty HTML shell (e.g., the HTML contains a <div id="root" /> in React) that most single-page applications start off with. For a smaller, client-side rendered application with fewer and smaller assets and data requirements, the application might have all the content rendered just in time for a search engine to crawl and index it. On the other hand, for a larger, client-side rendered application with many and larger assets and data requirements, the application needs a lot more time to download (and parse) all of these assets and fetch data from multiple API endpoints before rendering the content to the HTML shell. By then, the search engine might have already processed the page, regardless of the content's rendering status, and moved on to the next page. For sites that depend on being ranked at the top of a search engine's search results, such as news/media/blogging sites, the performance penalties and slower first contentful paint of client-side rendering may lower a site's ranking. This results in less traffic and business. Such sites should not client-side render entire pages worth of content, especially when the content infrequently (i.e., due to corrections or redactions) or never changes. Instead, these sites should serve the content already pre-generated as plain HTML. A common strategy for pre-generating content is static site generation . This strategy involves generating the content in advance (at build time) so that it is part of the initial HTML document sent back to the user's browser when the user first lands on the site. By exporting the application to static HTML, the content is created just once and reused on every request to the page. With the content made readily available in static HTML files, the client has much less work to perform. Similar to other static assets, these files can be cached and served by a CDN for quicker loading times. Once the browser loads the page, the content gets hydrated and maintains the same level of interactivity as if it was client-side rendered. Unlike Create React App , popular React frameworks like Gatsby and Next.js have first-class, built-in static site generation support for React applications. With the recent release of Next.js v12, Next.js applications build much faster with the new Rust compiler (compared to Babel, this compiler is 17x faster). Not only that, Next.js now lets you run code on incoming requests via middleware, and its APIs are compatible with React v18. In this multi-part tutorial, I'm going to show you how to... We will be building a simple, statically generated application that uses the Petfinder API to display pets available for adoption and recently adopted. All of the site's content will be pre-rendered in advance with the exception of pets available for adoption, which the user can update on the client-side. Home Page ( / ): Listings for Pet Animal Type ( /types/<type> ): Visit the live demo here: To get started, initialize the project by creating its directory and package.json file. Note : If you want to skip these steps, then run the command npx create-next-app@latest --ts to automatically scafford a Next.js project with TypeScript. Then, proceed to the next section of this tutorial. Install the following dependencies and dev. dependencies: Add a .prettierrc file with an empty configuration object to accept Prettier's default settings. Add the following npm scripts to the package.json file: Here's what each script does: At the root of the project directory, create an empty TypeScript configuration file ( tsconfig.json ). By running next , Next.js automatically updates the empty tsconfig.json file with Next.js's default TypeScript configuration. ( tsconfig.json ) Additionally, this command auto-generates a next-env.d.ts file at the root of the project directory. This file guarantees Next.js types are loaded by the TypeScript compiler. ( next-env.d.ts ) To further configure Next.js, create a next.config.js file at the root of the project directory. This file allows you to override some of Next.js's default configurations, such as the project's base Webpack configurations and mapping between incoming request paths and destination paths. For now, let's just opt-in to React's Strict Mode to spot out any potential problems, such as legacy API usage and unsafe lifecycles, in the application during development. ( next.config.js ) Similar to the tsconfig.json file, running next lint automatically installs the eslint and eslint-config-next dev. dependencies. Plus, it creates a new .eslintrc.json file with Next.js's default ESLint configuration. Note : When asked "How would you like to configure ESLint?" by the CLI, select the "Strict" option. ( eslintrc.json ) This application will be styled with utility CSS rules from the Tailwind CSS framework . If you are not concerned with how the application is styled, then you don't have to set up Tailwind CSS for the Next.js application and can proceed to the next section. Otherwise, follow the directions here to properly integrate in Tailwind CSS. To register for a Petfinder account, visit the Petfinder for Developers and click on "Sign Up" in the navigation bar. Follow the registration directions. Upon creating an account, you can find the API key (passed as the client ID in the request payload to the POST endpoint) and secret (passed as the client secret in the request payload to the POST endpoint) under your account's developer settings. Here, you can track your API usage. Each account comes with a limit of 1000 daily requests and 50 requests per second. At the root of the project directory, create a .env file with the following environment variables: ( .env ) Replace the Xs with your account's unique client ID and secret. The home page features a grid of cards. Each card represents a pet animal type catalogued by the Petfinder API: These cards lead to pages that contain listings of pets recently adopted and available for adoption, along with a list of breeds associated with the pet animal type (e.g., Shiba Inu and Golden Retriever for dogs). Suppose you have to build this page with client-side rendering only. To fetch the types of animal pet from the Petfinder API, you must: Initially, upon visiting the page, the user would be presented with a loader as the client... Having to wait on the API to process these two requests before any content is shown on the page only adds to a user's frustrations. Wait times may even be worst if the API happens to be experiencing downtime or dealing with lots of traffic. You could store the access token in a cookie to avoid sending a request for a new access token each time the user loads the page. Still, you are left with sending a request for a list of types each time the user loads the page. Note : For stronger security (i.e., mitigate cross-site scripting by protecting the cookie from malicious JavaScript code), you would need a proxy backend system that interacts with the Petfinder API and sets an HttpOnly cookie with the access token on the client's browser after obtaining the token from the API. More on this later. This page serves as a perfect example for using static site generation over client-side rendering. The types returned from the API will very rarely change, so fetching the same data for each user is repetitive and unnecessary. Rather, just fetch this data once from the API, build the page using this data and serve up the content immediately. This way, the user does not have to wait on any outstanding requests to the API (since no requests will be sent) and can instantly engage with the content. With Next.js, we will leverage the getStaticProps function, which runs at build time on the server-side. Inside this function, we fetch data from the API and pass the data to the page component as props so that Next.js pre-renders the page at build time using the data returned by getStaticProps . Note : In development mode ( npm run dev ), getStaticProps gets invoked on every request. Now, within the root of the project directory, create a pages directory, which will contain all of the page components. Next.js's file-system based router maps page components to routes. For example, pages/index.tsx maps to / , pages/types/index.tsx maps to /types and pages/types/[type.tsx] maps to types/:type ( :type is a URL parameter). Create three more directories: The Petfinder API documentation provides example responses for each of its endpoint. With these responses, we can define interfaces for the responses of endpoints related to pet animals, pet animal types and pet animal breeds. Create an interfaces directory within the shared directory. Inside of the interfaces directory, create a petfinder.interface.ts file. ( shared/interfaces/petfinder.interface.ts ) Note : This tutorial skips over endpoints related to organizations. Inside of the pages directory, create an index.tsx file, which corresponds to the home page at / . Let's build out the home page by first defining the <HomePage /> page component's structure. ( pages/index.tsx ) Let's create the <TypeCardsGrid /> component, which renders a grid of cards (each represents a pet animal type). The component places the cards in a 4x2 grid layout for large screen sizes (width >= 1024px), 3x3 grid layout for medium screen sizes (width >= 768px), 2x4 grid layout for small screen sizes (width >= 640px) and a single column for mobile screen sizes (width < 640px). ( components/TypeCardsGrid.tsx ) Let's create the <TypeCard /> component, which renders a card that represents a pet animal type. The card shows a generic picture of the pet animal type and a link to browse listings (recently adopted and available for adoption) of pet animals of this specific type. Note : The types returned from the Petfinder API do not have an id property, which serves a both a unique identifier and a URL slug (e.g., the ID of type "Small & Furry" is "small-furry"). In the next section, we will create a helper method that takes a type name and turns it into an ID. ( components/TypeCard.tsx ) Since the Petfinder API does not include an image for each pet animal type, we can define an enumeration ANIMAL_TYPES that supplements the data returned from the API with an Unsplash stock image for each pet animal type. To account for the images' different dimensions in the <AnimalCard /> component, we display the image as a background cover image of a <div /> and position the image such that the animal in the image appears in the center of a 10rem x 10rem circular mask. ( .enums/index.ts ) Like single-page applications that don't fully reload the page when navigating between different pages, Next.js also lets you perform client-side transitions between routes via the Link component of next/link . This component wraps around an <a /> element, and the href gets passed to the component instead of the <a /> element. When built, the generated markup ends up being just the <a /> element, but having an href attribute and having the same behavior as a <Link /> component.

Thumbnail Image of Tutorial Static Site Generation with Next.js and TypeScript  - Project Overview