Notes on using Next.js 14 with Pages router to generate static site from data returned by an API

Last updated on 18 May 2024

The above page has the key code required to generate a static site from blog posts using fetch to retrieve the blog posts using an API. I used it to write a test app (its Github repo) that retrieves latest 5 posts of this blog using Blogger API (v3.0), shows an index page with the post titles which are linked to appropriate pages showing post content. getStaticProps() and getStaticPaths() are used in this test app.

I deployed it to Vercel (and later deleted the deployment). The build seems to have generated the 5 static posts as they are listed under "Static Assets" under "Deployment Summary". Hmm. So deploying the static site was very straightforward in Vercel. I had tried deploying it to GitHub Pages without going through any special procedure for next.js apps and that, expectedly, did not work. So the easy Vercel deployment was pleasing. I later deleted the deployment as I am not clear whether the Google cloud API key that I had to use for Blogger API v3.0 is exposed in some way when deployed as a client side only app even if it is an environment variable.

A few notes about the above app:
  1. JSX escapes HTML (provided in a string). Two solutions are listed in: https://medium.com/@uigalaxy7/how-to-render-html-in-react-7f3c73f5cafc . The safer one is: https://www.npmjs.com/package/html-react-parser
  2. Nginx server issues
    1. By default nginx server does not try appending .html for a URL which does not have an extension. So the blog posts static site breaks as the links have only post id without ".html" extension. It works with npx serve@latest (which probably tries with .html extension appended and then finds the page and serves it). This issue with nginx server can be fixed with the following try_files line in nginx conf location entry:
        location / {
                  root html/out;
                  index index.html index.htm;
                  try_files $uri $uri.html $uri/ =404;
        }
      Ref: https://talk.plesk.com/threads/remove-php-and-html-from-url-nginx.369024/
    2. Also nginx server has to be told that root of the site is now html/out so that localhost/posts/xxx maps to posts/xxx in out directory. That's done by the root html/out; line above.
    With above conf file modifications, nginx is able to serve the static files in out directory as desired (like npx serve@latest).

Before doing the above blog posts app, I tried to code-along with the article: HOW TO USE NEXT.JS AS A STATIC SITE GENERATOR, https://pagepro.co/blog/how-to-use-next-js-static-site-generator/ , June 2023. As per the article, Next.js does not have any built-in data provider like Gatsby has GraphQL. But Apollo Client, https://www.apollographql.com/docs/react/ , seems to do the needful in terms of GraphQL support, for Next.js. The article also uses GitLab GraphQL API, https://docs.gitlab.com/ee/api/graphql/ . The article provides a link to the GitHub repo for the project covered in it.
 
When I first tried to code-along with the above article, I ran into many issues. I later realized that that was because I used create-next-app with its default setting of using App router. But the above article uses Pages router. Perhaps at the time the article was written, the default router used by create-next-app was the Pages router or the App router was not yet available. App router seems to have got introduced with Next.js 13. Current Next.js version is 14. 

I was able to resolve some of the initial issues when I was using the default App router and not knowing about Pages router also being available in Next.js. But I got stuck with the error of: "getStaticProps" is not supported in app/."... . Tried some stuff but that did not work. After I realized that I needed to use Pages router instead of default App router, I was able to progress quite rapidly in my code-along with the article. I have put up my code-along on GitHub.

Given below are some notes-points:
  1. Next.js 14 uses App Router by default. But one can choose No for App Router when using npx create-next-app and in that case, it seems to use Pages router and generates a directory and file structure which is significantly different from App Router.
  2. From https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#static-site-generation-getstaticprops: "In the pages directory, the getStaticProps function is used to pre-render a page at build time. This function can be used to fetch data from an external API or directly from a database, and pass this data down to the entire page as it's being generated during the build." So the getStaticProps() function is specific to pages directory which seems to be specific to Pages router. create-next-app does not create a pages directory if we use the default App router. I don't know if one can use both App router and pages router in the same project.
  3. Also, it seems that App router components have a different way of generating static pages. The same next.js docs link given above states, "In the app directory, data fetching with fetch() will default to cache: 'force-cache', which will cache the request data until manually invalidated. This is similar to getStaticProps in the pages directory." Hmm. Does that mean that if we use App Router and use default settings for fetch(), we will get the same benefits as if we use getStaticProps with Pages router? As of now, I don't have a clear answer to this.
  4. Should You Use Next.js Pages or App Directory?, https://medium.com/@CraftedX/should-you-use-next-js-pages-or-app-directory-38e803fe5cb4, Aug. 2023. The article states that App Router was introduced in Next.js 13. In its Pros & Cons section it says that Pages directory is good for static pages whereas App directory is better for dynamic pages.
  5. The file structure for create-next-app generated app. without App router is quite similar if not same as that shown in the article.
  6. In package.json, '"build": "next build && next export"' doesn't work. I had to use only '"build": "next build",'. Further I had to modify next.config.mjs to have, 'output: "export"', in its nextConfig const. Ref: Static Exports, https://nextjs.org/docs/app/building-your-application/deploying/static-exports. After these changes, the out directory was created on running 'npm run build'.
  7. Initially the out directory did not have index.html. This was related to a warning produced by npm run build about image optimization. Solution was to add "images: {  unoptimized: true, }," in next.config.mjs. Ref: Next Export does not generate any HTML files in /out dir #40240, https://github.com/vercel/next.js/issues/40240.
  8. The initial part of the article states that we can run our app by 'npm run build' but that seems to be an error as it simply builds the output files and exits. There is no server running to run the app/serve the website pages. Towards the bottom of the article, it mentions using 'npx serve' to see the static site. 'npx serve@latest out' was suggested by an error message I got [relevant entry of package.json: "start": "next start",] when I ran 'npm run start'.  'npx serve@latest out' seems to work.
  9. If 'npx serve@latest out' is running then 'npm run build' gives some error towards the end. Solution is to simply exit 'npx serve@latest out' before running 'npm run build'.
  10. This time getStaticProps() worked.
  11. The GitLab projects list worked but needed some tweaking where the github repo of the project: https://github.com/Pagepro/nextjs-static-site-generator , was very helpful. The Home component page needed this variation:
    import apolloClient from "@/src/setup/apolloClient";
    import { gql } from "@apollo/client";
  12. This stage of the project works with 'npm run dev' as well as with 'npm run build' followed by 'npx serve@latest out'. https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props states, "In development (next dev), getStaticProps will be called on every request." My test results were in line with this specification. The related query is:
    gql`
      query {
        projects(first: 10) {
          nodes {
            id
            name
            description
          }
        }
      }
    `
    1. This query can be run in https://gitlab.com/-/graphql-explorer (without gql and starting and ending `) and we see that re-running the query returns somewhat different results with some projects repeated between queries at times.
    2. When the nextjs program is run under dev we see somewhat different results when we refresh the page (as getStaticProps is getting called on every request) - this matches what we get with the explorer.
    3. But when the nextjs program is run as a static site using 'npm run build' followed by 'npx serve@latest out', we see the same 10 projects every time we refresh the page. This is as expected.
  13. I copied the out directory to nginx html directory, started nginx server and accessed localhost/out in the browser. It served the index page of the above project properly with same 10 projects shown on every refresh. 
So this phase of the code-along was successful.

The next phase of the code-along was to use getStaticPaths() along with getStaticProps() and generate details static pages for the 10 projects. The first time around I ran into some errors and my lack of knowledge about GraphQL was becoming an issue. So I decided to stop that activity and explore the above mentioned blog posts app which I was able to finish quite easily.

Later I did a second round of the above activity. This time it got done smoothly without any errors! I still do not have a good enough idea about GraphQL but the code works. What was new to me from Next.js perspective was the code's usage of "Catch-all Segments" (Next.js doc). Reading the Next.js documentation on it clarified the code, especially its usage of ... in the filename "[...fullPath].js", and split and join in the code.

During testing of the app, I saw that at times, the details page link of a project in the index page is broken though other links work. I think that may be due to the projects query for index page returning slightly different data from projects query for details [...fullpath] page. When I checked using GraphQL explorer I found that the same projects query returns slightly or sometimes very different data each time it is run.

Deployment to Vercel was easy: https://staticsitetest.vercel.app/ . As no API key is used here, I need not delete the Vercel deployment.

Comments