Notes on learning Next.js - 1st round and 2nd round

Last updated on 1 Oct 2024

Note: Third round done in Sept. 2024 is covered in Notes on 3rd round of Next.js official tutorial.

First round

2 to 8 May 2024
Have started with the offical Next.js tutorial: https://nextjs.org/learn . Was able to setup and run the example app. - https://nextjs.org/learn/dashboard-app/getting-started - without any issues.


import '@/app/ui/global.css'; - What does the @ symbol do here? It seems to refer to project root. @ symbol does not seem to part of JS but is added on by other package(s). In this tutorial case, it seems to be added on by Tailwind package. Ref: What does the @ symbol do in javascript imports?, https://stackoverflow.com/questions/42711175/what-does-the-symbol-do-in-javascript-imports .

clsx - "A tiny (239B) utility for constructing className strings conditionally.", https://www.npmjs.com/package/clsx ; Documentation: https://github.com/lukeed/clsx .

Next.js claims that if one uses its next/font, it manages fonts better avoiding CLS. Cumulative Layout Shift (CLS),  https://web.dev/articles/cls .

As per tutorial, one can use "next/image component to automatically optimize your images". Optimization includes responsive image on different screen sizes and preventing layout shift as images load.

In Chapter 6, after creating a Vercel a/c using Hobby and continue with GitHub, after approving/authorizing GitHub related dialog, Vercel took a long time to show the projects page (https://vercel.com/new/ravi-s-iyers-projects). Perhaps it took 5 to 10 minutes. Then I had to choose "Install" GitHub something in the Vercel page which led me to another approve/authorize GitHub dialog and after that approval, the list of GitHub projects got displayed in the Vercel page.
After that, deployment was straightforward though it took perhaps a minute or two to build and deploy the app. [Later, the tutorial adds login feature. Valid credentials are: Email: user@nextmail.com,
Password: 123456] https://nextjs-dashboard-nine-blue-35.vercel.app/ is the app link!

Hmm. Quite fascinating that Next.js is closely associated with a deployment platform! https://vercel.com/frameworks/nextjs states, "The native Next.js platform." .. "Made by the creators of Next.js, Vercel is designed to build, scale, and secure your Next.js apps."  ... It seems to have free service (Hobby) and paid service options.

The create database command sequence is slightly different from tutorial:
I used Storage tab (in Dashboard) and then chose Postgres Serverless SQL Create.
...
Connect(ing) to the created database (nextjs-dashboard-postgres) leads one to database info. page
...
Seeding data seemed to have only one issue of date column not being created in invoices. Don't know if this will cause an issue in next lessons of tutorial. [18 Sep. 2024: During 3rd round, when I visited Vercel postgres database pages, I could see the date column.]
...

In code-along for Chapter 7, section "Fetching data for <LatestInvoices/>", https://nextjs.org/learn/dashboard-app/fetching-data#fetching-data-for-latestinvoices , while showing latest 5 invoices in dashboard, on console, I got this error, "upstream image response failed for /customers/lee-robinson.png ResponseAborted" and similar errors for 3 other customers. But the images showed in the dashboard.

For later page refresh and also after app restart, I did not get the above error messages in the console.
...

I need to study the code to see what it does if it does not get a date for invoices (as date field/column is not shown in the database). I expected the latest 5 invoices part code to trip up on the date field but it did not! Instead it gave the above "upstream image response failed ..." errors on console (and showed some output on the page without any error message).

...

The Promise.all() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input's promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values. It rejects when any of the input's promises rejects, with this first rejection reason.
--- end extract ---
...
Vercel's sql template literal tag: https://vercel.com/docs/storage/vercel-postgres/sdk . For JS template literal tag support, see "Tagged templates" section in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates .
...
On using 'noStore();' in fetchLatestInvoices() in app/lib/data.ts (as instructed in section, "Making the dashboard dynamic", in chapter 8, https://nextjs.org/learn/dashboard-app/static-and-dynamic-rendering#making-the-dashboard-dynamic, I got the invoices error that I had expected earlier. Both console and app page showed the error, "Failed to fetch the latest invoices."

On commenting out the 'noStore();' line in fetchLatestInvoices() mentioned above, the error was not shown (neither on console nor or page)! Note that I still have the 'noStore();' line (not commented out) in other functions like fetchRevenue() and fetchCardData().

I suspect the issue is due to date column being absent in invoices table. As of now, I don't want to explore a fix to that as I think it may not be so relevant to my learning Next.js. [18 Sep. 2024: During 3rd round, when I visited Vercel postgres database pages, I could see the date column.]

...
Later as I added loading.tsx in dashboard, I faced some errors for other functions like fetchRevenue() and fetchCardData() (which still had noStore()). But that, I found out after few minutes, was due to Internet connection being down (my WiFi USB adapter sometimes stops working and I have to remove it from USB port and reconnect it to get it to work). This time around fetchLatestInvoices() worked even with noStore() statement in it!

Now loading.tsx in dashboard works as expected and all the above mentioned functions also seem to work as expected.
...

Moving page.tsx to (overview) in dashboard resulted in VSCode prompting for update of import statement. I haven't got the full hang of it but two paths had to be changed to include /(overview) in one of the files - .next/types/app/dashboard/page.ts.

...

Encountered "Error: Failed to fetch the latest invoices." again. Checked that the Internet connection was OK. On refresh of page, the error went away. So this seems to be a flaky problem.
...
Skipped Chapter 10, "Partial Prerendering (Optional)", as it is optional.
...
In Chapter 11, Adding Search and Pagination, for the section, Adding Pagination, https://nextjs.org/learn/dashboard-app/adding-search-and-pagination#adding-pagination , the tutorial omits instruction to uncomment some code in app/ui/invoices/pagination.tsx. However the file itself has a "// NOTE: comment in this code when you get to this point in the course" (in 2 places, IFIRC) which has to be done to have pagination feature in the app (for invoices).

A strange thing I noticed is that if I specify only the letter 'd' in the search invoices box, I get all the invoices! But single letter 'z' works as expected (showing only 2 invoices of Steph Dietz). 

[18 Sept. 2024: The related SQL where clause is:
      WHERE
        customers.name ILIKE ${`%${query}%`} OR
        customers.email ILIKE ${`%${query}%`} OR
        invoices.amount::text ILIKE ${`%${query}%`} OR
        invoices.date::text ILIKE ${`%${query}%`} OR
        invoices.status ILIKE ${`%${query}%`}
      ORDER BY invoices.date DESC
    ...
---
It seems that it is invoice.status with values of 'pending' or paid' that returns all invoices (each page data controlled by LIMIT and OFFSET clauses) when only 'd' is specified in search box. 'p' behaves same way.
end 18 Sept. 2024 update]
...
In Chapter 12, Mutating Data, the first time I tried to Create an invoice, it failed. I think I may have typed in some invalid character somewhere. As error handling is not yet implemented at this stage, I did not get an error message and the form continued to be displayed. The next two attempts to create invoice went through smoothly. I was surprised to see the date being created correctly for the invoices and I could see valid dates for earlier invoices too! But, IFIRC, I was not able to see those dates directly in the database (through Vercel website, IFIRC). [18 Sep. 2024: During 3rd round, when I visited Vercel postgres database pages, I could see the date column.]
...
In chapter 13, Handling Errors, the try-catch block statements have catch only returning an object with message property but not throwing it as an error object. So how will the invoking code e.g. Form component of app/ui/invoices/create-form.tsx which has "<form action={createInvoice}>" statement, know that an error has occurred and retrieve the message property of the object returned? 
Tried to create an error by creating an invoice without choosing a customer and not specifying Status radio button (neither pending nor paid). Got an app crash error message on the app, stating "Unhandled Runtime Error" with the statement being CreateInvoice.parse. The error details are provided as an object array which seems to say that for customerId, the error message is: "Expected string, received null" and for status, the error message is: "Expected 'pending' | 'paid', received null".

Note that I have not yet coded the error.tsx page.

After creating the error.tsx page, when I did the same operation mentioned above, I was shown the error.tsx page with a red coloured box at bottom left of window (over the Sign Out box), stating "1 error". Clicking on the box outside the 'x' button in it, showed the error details as mentioned in earlier case above.

...
In Chapter 14, Improving Accessibility (and Form Validation):
Added error message handling for create invoice. That works.
To save time, skipped doing it for edit invoice. May do it later on.
...
In chapter 15, Adding Authentication, I faced an error on running the following:
openssl rand -base64 32

I tried: npm i openssl
That did not work.


Downloaded above. Unzipped and then ran the openssl.exe from its bin directory 'openssl rand -base64 32'. It output some long code/string but also gave some error, IFIRC, about “unable to write ‘random state'”. 

Eventually I landed on: OpenSSL is not recognized, https://devbrains.tn/tutorials/openssl-is-not-recognized which provided an Download OpenSSL 3.0 link: https://www.mediafire.com/file/3nklzep60ezej7j/Win64OpenSSL_Light-3_0_0.zip/file . I downloaded it, ran the setup file in it which installed a "Win64 OpenSSL Command Prompt" accessible from Search menu next to Start in Windows.

Opened "Win64 OpenSSL Command Prompt" and ran 'openssl rand -base64 32'. It output a long code/string and gave no error. So looks like now I have a working openssl command.
...

The code changes in this chapter are quite complex at least at first look. I decided to not spend time to understand all the code changes now. I think I will do it later, either in a 2nd round of the whole tutorial or perhaps earlier. I wanted to get all the code changes made and run the app. with this authentication.

I got a few compilation-type errors which were due to some mistakes in my copying the modified code from tutorial chapter to my project code. I was able to fix that without too much difficulty though the TypeScript errors are still rather new to me.

Then I tripped up on port 4000 that I use instead of 3000 that the app seems to be setup to use. I changed one entry in .env file to use 4000 instead of 3000: AUTH_URL=http://localhost:4000/api/auth

On running the app, I faced an odd issue of the app going to http://localhost:3000/dashboard and not finding it (as the app is running on port 4000). Refresh of the page did not help. But restart of the app. resolved the issue. So looks like .env changes need restart of app to get reflected.

Now the app. is working as expected. Sign in with valid credentials takes user to dashboard and shows all the data. Invalid credentials show error message. Signed out user going to dashboard gets redirected to login page. Great to see this as I think the Next.js components and lib. functions are making it much easier than if one directly coded all this stuff. ... The app. lacks a user registration facility but perhaps Next.js provides some support there too.

While the app was working there was a repetitive warning on the console:
"[auth][warn][env-url-basepath-redundant] Read more: https://warnings.authjs.dev#env-url-basepath-redundant" On visiting the specified link, the info. was, "AUTH_URL (or NEXTAUTH_URL) and authConfig.basePath are both declared. This is a configuration mistake - you should either remove the authConfig.basePath configuration, or remove the pathname of AUTH_URL (or NEXTAUTH_URL). Only one of them is needed."

I searched for authConfig.basePath in all files of project (using VSCode facility) but could not get any hits. So that seems to come from one of the packages the app is using. I tried commenting out the AUTH_URL line in .env file followed by restarting the app. With that, the above warning stopped appearing and the app. authentication stuff is working as expected!
...
In chapter 16, Adding Metadata, I skipped the practice part of adding title metadata to 'other' pages like login, dashboard etc. Perhaps I will do that in a 2nd round.
...
Chapter 17, Next Steps, has "some resources to continue exploring Next.js". The tutorial proper gets over with chapter 16. So I have finished the 1st round of this tutorial (on 8 May 2024). I don't know when I will do a 2nd round. Perhaps I may do it on demand i.e. if some real-life project needs me to use Next.js.

Update on 8 May 2024: I also pushed the tutorial repo to GitHub. After the auto-update on Vercel, the app https://nextjs-dashboard-nine-blue-35.vercel.app/ [Valid credentials are: Email: user@nextmail.com, Password: 123456] was breaking as after login, it showed a 404 not found error, irrespective of whether the credentials were keyed in correctly or not. The url was "https://nextjs-dashboard-nine-blue-35.vercel.app/undefined". 
I added AUTH_SECRET environment variable and its value (from .env) to Vercel deployed project. [Vercel project dashboard -> Settings -> Environment Variables]
Then I had to redeploy the project for new settings to take effect. [Vercel project dashboard -> Deployments -> Latest deployment -> Vertical 3 dots icon next to Visit button -> Redeploy]
That fixed the problem and the above app deployed on Vercel works as expected.
------

Second round

Started around 18 May 2024
import { ArrowRightIcon } from '@heroicons/react/24/outline';

Browse at Heroicons.com → https://heroicons.com/  (search for ArrowRight and not ArrowRightIcon)

"Icons use an upper camel case naming convention and are always suffixed with the word Icon."
Browse the full list of icon names on UNPKG → https://unpkg.com/browse/@heroicons/react@2.1.3/24/outline/

...

In Chapter 5, section "Pattern: Showing active links", https://nextjs.org/learn/dashboard-app/navigating-between-pages#pattern-showing-active-links, it states, "Since usePathname() is a hook, you'll need to turn nav-links.tsx into a Client Component. Add React's "use client" directive to the top of the file, then import usePathname() from next/navigation".

In the first round of going through this tutorial, I had deliberately skipped digging in on the "use client" stuff. This time around I felt I should dig in. The doubt in my mind was that I never used "use client" even when I used React hooks like useState() in the React programs I wrote as I was learning React. React has the feature of server components which Next.js uses. I have not used React server components while I was learning React. 

The "Server and Client Components" chapter of the React introduction in Next.js, https://nextjs.org/learn/react-foundations/server-and-client-components, was the page to study. But I felt I should at least quickly go through the earlier part of the React introduction and so I did that.

It was a good, quick and easy refresher of React for me till I came to Chapter 9, Installing Next.js, https://nextjs.org/learn/react-foundations/installation. In this chapter, a HomePage component code developed in earlier chapters is modified to use npm packages of react, react-dom and next via import statements (earlier versions used CDN react and react-dom scripts), having only the HomePage and its container components without html and body elements, using app folder etc. which is the typical Next.js setup. Then, after updating package.json with script for next dev, the npm run dev command is run which gives a compilation error for useState() suggesting that it should be made a Client Component.

I wanted to confirm that it is Next.js that introduces this problem in the code. So I adapted the HomePage and its contained component to work with react-scripts (index.js moved to src directory and renamed App.js; its component renamed to App). Further public/index.html was copied from an existing React project and src/index.js was also copied from the same project but modified to have bare minimum code.

Irrespective of whether next.js package is installed or not, on npm run start, the app. runs as expected. This confirmed that the "use client" error that crops up for the equivalent component code when compiled in next.js, does not crop up in standard react without next. 
...

"Server and Client Components", https://nextjs.org/learn/react-foundations/server-and-client-components states, "As you learned in the last chapter, Next.js uses Server Components by default - this is to improve your application's performance and means you don't have to take additional steps to adopt them." Hmm. So React seems to use Client Components by default but Next.js uses Server Components by default, if I got that right.

The chapter explains the fix to the useState() issue which is straightforward. I tried the changes and it works as expected. Now I think I have got a better understanding of React Server and Client Components in the context of Next.js.
...
app/lib/data.ts has:
  const data = await sql<Revenue>`SELECT * FROM revenue`;
---

The tutorial page Fetching Data, https://nextjs.org/learn/dashboard-app/fetching-data as well as the Vercel SDK Reference page for sql template literal tag, https://vercel.com/docs/storage/vercel-postgres/sdk#sql do not seem to have an example or explanation for the angle brackets part just after sql in the statement above. As Revenue is a type defined in app/lib/definitions.ts, I guess there is some Typescript stuff that comes into play here but I don't know exactly what it does.

----
"With static rendering, data fetching and rendering happens on the server at build time (when you deploy) or during revalidation. The result can then be distributed and cached in a Content Delivery Network (CDN)."

In app/lib/data.ts, most of the fetch functions use unstable_noStore to opt out of static rendering. These fetch functions include:
fetchRevenue, fetchLatestInvoices and fetchFilteredCustomers.

The only fetch functions that do not use unstable_noStore and so are statically rendered, are:
fetchCustomers and getUser.
getUser gets a user based on email id.
When do fetchCustomers and getUser get revalidated, or do they not get revalidated at all? Perhaps the limited tutorial app. does not revalidate these functions.

...

"Adding Search and Pagination", https://nextjs.org/learn/dashboard-app/adding-search-and-pagination tutorial page involves interaction between server and client components. 


app/dashboard/invoices/page.tsx has:
      <Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}>
I don't understand why key is being specified for Suspense above.
[18 Sept. 2024: It seems to be used to force React to reset all state of this Suspense component when key changes. Ref: Resetting all state when a prop changes, https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes .]

app/dashboard/(overview)/page.tsx uses Suspense without key, like:
        <Suspense fallback={<CardsSkeleton />}>

...

/app/ui/search.tsx is a client component (has 'use client'; statement). It uses replace (from useRouter) as follows:
    replace(`${pathname}?${params.toString()}`);

The tutorial page states, "The URL is updated without reloading the page, thanks to Next.js's client-side navigation (which you learned about in the chapter on navigating between pages."

Now the question is when does reload of the invoices page (app/dashboard/invoices/page.tsx) which is a server component and which uses the above mentioned search client component, happen. I think that once the search component updates the URL, the invoices Page component would be invoked with the updated URL. The Page component contains the Table component (/app/ui/invoices/table.tsx) which initiates the database query by using fetchFilteredInvoices function. Note that fetchFilteredInvoices() uses unstable_noStore to "declaratively opt out of static rendering and indicate a particular component should not be cached", https://nextjs.org/docs/app/api-reference/functions/unstable_noStore

So will Table component as well as invoices Page component be dynamically rendered? I think so. The tutorial does not seem to give a clear picture here.

The Pagination component is also a client component which is contained in invoices Page component. In this context, the tutorial states, "You don't want to fetch data on the client as this would expose your database secrets (remember, you're not using an API layer). Instead, you can fetch the data on the server, and pass it to the component as a prop." Does this imply that if we make a mistake of using a function like fetchxxx in a client component, we would be inadvertently introducing a security issue? Does Next.js compiler at least warn us about such security issues?

The Paginations component seems to be setting href attribute of Link component with appropriate query string for the various page Links shown to user. The pathname would be that of the invoices page. So like in the case of search component, changing the query string part of URL may be resulting in calling of the invoice Page function which is a server component and which would directly/indirectly call a fetch function to get appropriate data from the database.

That finishes the notes for the tutorial page, "Adding Search and Pagination".

Notes on Chapter 12, Mutating Data, https://nextjs.org/learn/dashboard-app/mutating-data :

"React Server Actions allow you to run asynchronous code directly on the server. They eliminate the need to create API endpoints to mutate your data. Instead, you write asynchronous functions that execute on the server and can be invoked from your Client or Server Components."

form element's action attribute can be set to (invoke a) React Server Action.

...
const CreateInvoice = FormSchema.omit({ id: true, date: true });
My understanding: CreateInvoice Zod schema is same as Zod FormSchema without id and date 'keys'
Don't know what role the "true" specification does in above.
...
...
app/ui/invoices/create-form.tsx is a client component. Its form element directly/indirectly has createInvoice React Server Action as its action attribute. So here we see a form client component invoke an async server function that inserts data into database (without going through any REST API stuff).

  const updateInvoiceWithId = updateInvoice.bind(null, invoice.id);
My understanding now is that using '<form action={updateInvoiceWithId}>' will result in form submission invoking updateInvoice() with first argument as invoice.id, followed by other argument(s) (in this case formData of type FormData). Also note that null argument passed to bind corresponds to 'this' parameter of invoked function (updateInvoice()).

--- end notes on Chapter 12 ---

Chapter 14 has following code:
errors: validatedFields.error.flatten().fieldErrors,

Error handling for forms, https://zod.dev/ERROR_HANDLING?id=error-handling-for-forms covers flatten() method. I have not studied it in detail now but when needed I will do so.
...


Auth.js is a runtime agnostic library based on standard Web APIs that integrates deeply with multiple modern JavaScript frameworks to provide an authentication experience that’s simple to get started with, easy to extend, and always private and secure!
...
There are 4 ways to authenticate users with Auth.js:
OAuth authentication (Sign in with Google, GitHub, LinkedIn, etc…)
Magic Links (Email Provider like Resend, Sendgrid, Nodemailer etc…)
Credentials (Username and Password, Integrating with external APIs, etc…)
WebAuthn (Passkeys, etc…)
-----

OAuth (short for "Open Authorization"[1][2]) is an open standard for access delegation, commonly used as a way for internet users to grant websites or applications access to their information on other websites but without giving them the passwords.[3][4] This mechanism is used by companies such as Amazon,[5] Google, Meta Platforms, Microsoft, and Twitter to permit users to share information about their accounts with third-party applications or websites.

Generally, the OAuth protocol provides a way for resource owners to provide a client [application] with secure delegated access to server resources. It specifies a process for resource owners to authorize third-party access to their server resources without providing credentials. Designed specifically to work with Hypertext Transfer Protocol (HTTP), OAuth essentially allows access tokens to be issued to third-party clients by an authorization server, with the approval of the resource owner. The third party then uses the access token to access the protected resources hosted by the resource server.[2]

--------

https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher gives some background of the middleware matching used in middleware.ts

In auth.config.ts in authorized callback, I did not understand the usage of !! in the following statement:
     const isLoggedIn = !!auth?.user;
...
Above file also uses satisfies operator of Typescript. Ref: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html

In this 2nd round I was able to get a decent understanding of the authentication code though I have not delved into some details like middleware matching.
...

Finished the 2nd round on 23rd May 2024.

==== Old entry below (retained just in case it helps somebody else) ====

20 Apr. 2024: A day or two earlier I had created the starter app. IFIRC, I used 'npx create-next-app test1' to create the boilerplate/starter app. Today I tried to run the app using 'npm start' but it gave some error. Then I read up some documentation and saw that 'npm run dev' is the command to start the app - that worked. Now I think I had used 'npm run dev' a day or two earlier when I created the starter app. Note that package.json does have a start script and so I thought 'npm start' would do the job.


Comments

Archive