Differences

This shows you the differences between two versions of the page.

Link to this comparison view

se:labs:04 [2024/11/01 14:05]
avner.solomon
se:labs:04 [2024/11/01 19:06] (current)
cosmin.dumitrache [Lab 04 - Next.js: SSR and SSG]
Line 1: Line 1:
-====== Lab 04 - Next.js: SSR and SSG ======+====== Lab 04 - Backend ​======
  
 ===== Single Page Applications (SPAs) ===== ===== Single Page Applications (SPAs) =====
Line 34: Line 34:
 {{:​se:​labs:​se-next-ssg.png?​700|}} {{:​se:​labs:​se-next-ssg.png?​700|}}
  
-===== SSG Implementation ​=====+===== Server/​Client Components ​=====
  
-When generating content ​at build time, Next.js ​also supports fetching external data beforehandThat can be performed ​by defining ​the **//getStaticProps//** inside the file where a page is defined (more on pages later).+Next.js 15 goes one step further and instead of looking ​at the pages as SSG/SSR it instead using new react concepts as Server Components and Client Components. In Next.js 15, all your components are by default server componentswhich means Next.js ​will try to populate them when rendering the page before sending it to the client'​s browserSince all components are by default server components and are rendered on the server, there is no hydration step necessary (it still takes place when possible) and your pages will work even with javascript disabled. In these situations the data fetching happens on the server. To find out more about the advantages of server components read the [[https://nextjs.org/docs/app/​building-your-application/​rendering/​server-components|Server Components]] ​page.
  
-<​code ​javascript+On the other hand if your page needs some level of interactivity using javascript/react on the client side (and you need to make use of methods like useState/​useEffectyou will need to upgrade your component to a [[https://nextjs.org/docs/​app/​building-your-application/​rendering/​client-components|Client Component]]. In these components you are allowed to import from the react module, in server components you are not. 
-export default function Home(props+
-  return <​p>​Little known fact{props.fact}</p>; +
-}+
  
-export async function getStaticProps() +It is always recommended to use serverside components unless you need actual interactive components on the client side. For example imagine a blog written in Nextjs with react. Almost all pages will be static with (latest articles page, single article page with comment section, etc.), except for a few widgets relevant to the logged in user. In this situation we would design all our pages as static components, rendered fully on the server with links sending to the user to another page. For example, if the hydration step takes place, nextjs will considered the page "​enhanced"​ and request a smaller amount of data to populate the new page (of the article, when coming form the home page), on the other hand if the hydration did not take place (due to a variety of factors like: js disabled, low internet speed and js did not load yet) clicking the link will redirect the browser to the article'​s URL which in change will request a new full page render from the server. This way, server components end up supporting both situations when the page hydrates or not. That is why the default in nextjs 15 is server components.
-  const fact = getAwesomeFactFromApi();+
  
-  // These are the same props that are passed as an argument to the component +For a more in-depth comparison between ​server and client ​components please read [[https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns|Server ​and Client ​Composition Patterns]]
-  return { +
-    props: { fact } +
-  }; +
-+
-</​code>​ +
- +
-<note important>​ +
-The **//​getStaticProps//​** is executed on the server ​side and it is not included in the client ​bundle. +
-</note> +
- +
-===== SSR Implementation ===== +
- +
-Likewise, external data can be fetched at request time, before bundling the web page and returning it to the client. To do that, the **//​getServerSideProps//​** function must be defined in the same file where the page is defined (again, more on pages later). +
- +
-<code javascript>​ +
-export default function UserProfile({ firstName, lastName }) { +
-  return <​p>​Hi! My name is {firstName} {lastName}.<​/p>; +
-+
- +
-export async function getServerSideProps() { +
-  const userId = ... // get the userId somehow (e.g. from the query param) +
-   +
-  const profileData = fetchUserData(userId);​ +
- +
-  ​// These are the same props that are passed as an argument to the component +
-  return { +
-    props: { +
-      firstName: profileData.firstName,​ +
-      lastName: profileData.lastName,​ +
-    } +
-  }; +
-+
-</​code>​ +
- +
-<note important>​ +
-The **//​getServerSideProps//​** is executed on the server side as well and it is not included in the client bundle. However, the server'​s response time will be higher because the data must be recomputed with each request and hence, it cannot be cached by a CDN. +
-</​note>​ +
-===== Client-Side Rendering =====+
  
-Conveniently,​ if the **//​getStaticProps//​** and **//​getServerSideProps//​** are not defined, Next.js will automatically use the SSG approach. However, if further tweaks must be performed exclusively on the client side after the static page is received, that can be done using hooks as one normally does in React. 
  
-{{:​se:​labs:​se-next-client-side-rendering.png?​700|}} 
- 
-Next.js recommends fetching data on the client using [[https://​swr.vercel.app/​|SWR]],​ a popular React hook just for that. 
- 
-<code javascript>​ 
-import useSWR from '​swr';​ 
- 
-function Dashboard() { 
-  // "​fetcher"​ can be Axios 
-  const { dashboardData,​ error } = useSWR('/​api/​dashboard/​stats',​ fetcher); 
- 
-  if (error) return <p>An error occurred!</​p>;​ 
-  if (!dashboardData) return <​div>​Loading dashboard data...</​div>;​ 
-  return ( 
-    <div> 
-      <​h1>​My Dashboard</​h1>​ 
-      <​p>​Visitors:​ {dashboardData.visitors}</​p>​ 
-      <​p>​CPU usage: {dashboardData.cpu}%</​p>​ 
-      <​p>​Memory usage: {dashboardData.memoryMB}MB</​p>​ 
-    </​div>​ 
-  ); 
-} 
-</​code>​ 
 ===== Next.js Routing ===== ===== Next.js Routing =====
  
-Next.js has an opinionated way of handling the application routes. ​Each //​**.js**//,​ //​**.jsx**//,​ //​**.ts**//,​ or //**.tsx**// file that sits in the **pages** directory represents ​page and from a React perspective,​ it is just another component. However, these are the only files in which the **//​getServerSideProps//​** and **//​getStaticProps//​** functions can be written to inform Next.js whether those pages are using SSR or SSG. +Next.js has an opinionated way of handling the application routes. ​In order to define new website pages you need to create a page.tsx file for each in a int app folder or in a sub folder representing ​the path you want to implement ​
- +
-By default, each directory represents ​part of the resulting URL of your page. That also applies ​to those files whose names are not **//​index//​**. Hence, the following structures are equivalent:+
  
 <code javascript>​ <code javascript>​
-  * pages/index.jsx +  * /app/page.tsx         //- will define the hopage 
-  * pages/blog/index.jsx +  * /app/about/page.tsx   //- will define the page located at domain.com/about
-  * pages/blog/first-post/index.jsx+
 </​code>​ </​code>​
  
-<code javascript>​ +You can find more information in the [[https://nextjs.org/docs/app/building-your-application/routing/pages|Pages section]]
-  * pages/index.jsx +
-  * pages/blog/index.jsx +
-  * pages/blog/first-post.jsx +
-</code> +
- +
-And they both generate the URLs: +
- +
-<code javascript>​ +
-  * / +
-  * /blog +
-  * /blog/first-post +
-</code> +
- +
-<note tip>Page components can still make use of other components written in the **components** directory.<​/note>+
  
 ===== Dynamic Routes ===== ===== Dynamic Routes =====
  
-Next.js also supports **dynamic routes** which are useful when the route depends on a URI parameter. Here are some examples of URLs where this might come in handy:+Next.js also supports ​[[https://​nextjs.org/​docs/​app/​building-your-application/​routing/​dynamic-routes|**dynamic routes**]] which are useful when the route depends on a URI parameter. Here are some examples of URLs where this might come in handy:
  
 <code javascript>​ <code javascript>​
-  * /​blog/:​post-name+  * /​blog/:​post-id
   * /​profile/:​user-id   * /​profile/:​user-id
   * /​videos/:​video-id/​statistics   * /​videos/:​video-id/​statistics
Line 153: Line 70:
  
 <code javascript>​ <code javascript>​
-  * /blog/[post-name].jsx +  * /app/article/[article-id]/page.tsx 
-  * /profile/:[user-id].jsx +  * /app/user/[user-id]/page.tsx 
-  * /​videos/​[video-id]/​statistics.jsx +  * /app/​videos/​[video-id]/​statistics.tsx 
-  * /​questions/​[...all].jsx+  * /app/​questions/​[...all].jsx
 </​code>​ </​code>​
  
Line 194: Line 111:
 </​code>​ </​code>​
  
-As an alternative,​ if you want to navigate between pages dynamically,​ from your JavaScript logic, you can do that with **//​useRouter//​**.+As an alternative,​ if you want to navigate between pages dynamically ​in a client component, from your JavaScript logic, you can do that with **//​useRouter//​**.
  
    
 <code javascript>​ <code javascript>​
-import { useRouter } from 'next/router';+'use client'​ 
 +import { useRouter } from 'next/navigation';
  
 function Blog({ completePostpath }) { function Blog({ completePostpath }) {
Line 216: Line 134:
  
 <note tip> <note tip>
-Any page contained by a **//Link//** will be prefetched by default (including the corresponding data) if SSG is used. The corresponding data for SSR pages is fetched only when the **//Link//** is clicked.+Read more about linking and navigation [[https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating|here]].
 </​note>​ </​note>​
- 
-===== Disable SSR/SSG ===== 
- 
-Next.js also allows you to disable the default behavior of pre-generating the HTML and CSS code at build time by explicitly indicating that you want to dynamically load a certain component on the client side. The rendering of that component will be performed by executing JavaScript code in the browser, just like it happens with a classic SPA. This approach might be useful when you need to access client-side specific libraries (e.g. window). 
- 
-<code javascript>​ 
-import dynamic from '​next/​dynamic';​ 
- 
-const DynamicHeader = dynamic(() => import('​../​components/​header'​),​ { 
-  ssr: false, 
-}); 
- 
-function Home() { 
-  return ( 
-    <div> 
-      <​DynamicHeader/>​ 
-      <​h1>​Welcome to my page!</​h1>​ 
-    </​div>​ 
-  ) 
-} 
-  
-export default Home; 
-</​code>​ 
  
 ===== Layouts ===== ===== Layouts =====
  
-If you want to reuse components on multiple pages, **layouts** come in handy. For instance, your app might have a header and a footer on each page:+If you want to reuse components on multiple pages, ​[[https://​nextjs.org/​docs/​app/​building-your-application/​routing/​layouts-and-templates#​layouts|**layouts**]] come in handy. For instance, your app might have a header and a footer on each page:
  
 <code javascript>​ <code javascript>​
-// layouts/layout.jsx+// app/article/layout.tsx 
 +// this layout will be the layout for all pages in the article folder and it's children
  
 import Navbar from '​..components/​navbar';​ import Navbar from '​..components/​navbar';​
 import Footer from '​..components/​footer';​ import Footer from '​..components/​footer';​
  
-export default function ​Layout({ children }) {+export default function ​ArticleLayout({ children }) {
   return (   return (
     <>     <>
Line 263: Line 159:
 </​code>​ </​code>​
  
-<code javascript>​ 
-// pages/​_app.jsx 
  
-import Layout from '../layouts/layout';​+Multiple layouts can also be used on a per-page basisYou can find more about that [[https://​nextjs.org/docs/app/​building-your-application/​routing/​layouts-and-templates#​nesting-layouts|here]].
  
-export default function ​Home({ Component, pageProps }) {+ 
 +===== Data fetching ===== 
 +In both server and client components [[https://​nextjs.org/​docs/​app/​building-your-application/​data-fetching/​fetching|data is fetched using]] the native fetch method. That being said it is recommended to avoid using it directly in combination with fetch inside client components but rather use a React library optimized for this pattern like [[https://​swr.vercel.app|SWR]] which adds support for use SWR 
 + 
 +<code javascript>​ 
 +// server side component fetch 
 +export default ​async function ​Page() { 
 +  let data = await fetch('​https://​api.vercel.app/​blog'​) 
 +  let posts = await data.json()
   return (   return (
-    <Layout+    <ul
-      <Component ​{...pageProps} /> +      ​{posts.map((post) => ( 
-    </Layout+        ​<li key={post.id}>​{post.title}</​li>​ 
-  );+      ))} 
 +    </​ul>​ 
 +  ) 
 +
 +</​code>​ 
 + 
 +<code javascript>​ 
 +// client side component fetch with useEffect 
 +'use client'​ 
 +  
 +import { useState, useEffect } from '​react'​ 
 +  
 +export function Posts() { 
 +  const [posts, setPosts] = useState(null) 
 +  
 +  useEffect(() => { 
 +    async function fetchPosts() { 
 +      let res = await fetch('​https://​api.vercel.app/​blog'​) 
 +      let data = await res.json() 
 +      setPosts(data) 
 +    ​} 
 +    fetchPosts() 
 +  }, []) 
 +  
 +  if (!posts) return <​div>​Loading...<​/div> 
 +  
 +  return ( 
 +    ​<​ul>​ 
 +      {posts.map((post) => ( 
 +        <li key={post.id}>​{post.title}</li> 
 +      ))} 
 +    </ul
 +  ) 
 +
 +</​code>​ 
 + 
 +<code javascript>​ 
 +//swr 
 +import useSWR from '​swr'​ 
 +  
 +function Profile() { 
 +  const { data, error, isLoading } = useSWR('/​api/​user',​ fetcher) 
 +  
 +  if (error) return <​div>​failed to load</​div>​ 
 +  if (isLoading) return <​div>​loading...</​div>​ 
 +  return <​div>​hello {data.name}!</​div>​ 
 +
 +</​code>​ 
 + 
 + 
 +===== Server Actions and Mutations ===== 
 + 
 +[[https://​nextjs.org/​docs/​app/​building-your-application/​data-fetching/​server-actions-and-mutations|Server Actions]] are asynchronous functions that are executed on the server. They can be called in Server and Client Components to handle form submissions and data mutations in Next.js applications. 
 + 
 +Server actions can be used in forms with the action attribute <form action={serverAction}></​form>,​ which will call your serverAction(formData:​ FormData} function and pass all the forms inputs to it. 
 + 
 +<code javascript>​ 
 +// Inside server components, actions can be coded directly as 
 +// a function as long as the 'user server'​ directive is written, 
 +// telling next.js that the given function should run only on the server. 
 + 
 +// This example does not use the data to process it in any way, 
 +// it is just showing you how you can process the data 
 + 
 +// It is recommended unless the code is trivial and small, 
 +// to put all actions in a separate file implementing the logic of the components 
 + 
 +export default function Page() { 
 +  async function createInvoice(formData:​ FormData) { 
 +    'use server'​ 
 +  
 +    const rawFormData = { 
 +      customerId: formData.get('​customerId'​),​ 
 +      amount: formData.get('​amount'​),​ 
 +      status: formData.get('​status'​),​ 
 +    } 
 +  
 +    // mutate data 
 +    // revalidate cache 
 +  } 
 +  
 +  return <form action={createInvoice}>​...</​form>​ 
 +
 +</​code>​ 
 + 
 +While actions can't be used directly inside a client component, they can be defined in a separate file and then used directly in the component. Behind the scenes, this will send a fetch request to the server, execute the function on the server, and return the result to the browser. 
 + 
 +<code javascript>​ 
 +// app/​actions.ts 
 +'use server'​ 
 +  
 +export async function create() { console.log('​button clicked'​) } 
 +//This code will run on the server and show in the server console 
 +//Nothing will be written in the browser'​s console 
 +</​code>​ 
 + 
 +<code javascript>​ 
 +// app/​coomponents/​some-button.tsx 
 +'use client'​ 
 +  
 +import { create } from '​@/​app/​actions'​ 
 +  
 +export function Button() { 
 +  return <button onClick={() => create()}>​Create</​button>​
 } }
 </​code>​ </​code>​
  
-Multiple layouts can also be used on a per-page basis. You can find more about that [[https://​nextjs.org/​docs/​basic-features/​layouts|here]]. 
  
-<note tip>It is good practice to place all the layouts in a dedicated **layouts** directory, like in the example above.</​note>​ 
 ===== Next.js Configuration ===== ===== Next.js Configuration =====
  
Line 288: Line 291:
  
 <note tip> <note tip>
-**Material UI** is a popular component library for React. All the necessary dependencies have already been included in the project ​so you can make use of the readily available ​components. You can check out the [[https://​mui.com/​material-ui/​|official docs]] ​to understand how can you include those components in your code+For this project, we will use the default empty nextjs app template together with the components ​you created in the previous LabSince they are written with front-end interactivity in mind, we will upgrade them to client ​components. We will be storing the todolist data in memory on the server and mutate it with server actions/​mutations
- +You will find the components in the app/components folder 
-For this project, we will use the [[https://​documenter.getpostman.com/​view/​1946054/​S11HvKSz#​1e9464c5-6bef-421e-8f07-1a3523a2d98a|Cat Facts API]]. ​The GET requests ​will be performed using [[https://​stackoverflow.com/​questions/​68150039/​use-axios-in-getstaticprops-in-next-js|Axios]].+The actions ​will be in the app/actions.ts file together with TODOs
 </​note>​ </​note>​
  
-  - Download the {{:se:labs:se-lab5-tasks-v2.zip|project archive}} for this lab and run **//npm install//** and **//npm run dev//**. +  - Download the  {{:se:labs:lab-4.zip|project archive}} for this lab and run **//npm install//** and **//npm run dev//**. 
-  - Link the home page and the ///tasks// page together (the tasks page should be reachable from the home page and vice-versa). +  - Take a look over the app/components/ToDoList.tsx file and compare it with your previous implementation for the React only app 
-  - Create an SSR page at ///cats// that displays 3 random cat factsUse a dedicated Fact component. +  - Take a look over the app/page.tsx wich implements ​the homepage ​of your website and notice how the ToDoList component is pre initialized with data, allowing it to be rendered on the server on the first page load
-  - Create an SSG page at ///​cats/​breeds//​ that displays ​the first 10 breeds in alphabetic order. Use a dedicated Breed component. +  - Implement ​the required mutations in the app/actions.tsx file in order to fully reimplement ​the functionality of the react appAll missing parts are marked with TODO comments 
-  - Create a layout for your entire application that adds a header containing a cat logo for all of your pages (you already have the logo in the **public** directory)+ 
-  - Link the homepage with the ///cats// page using the **//​Link//​** component and the ///​cats/​breeds//​ page using the **//​useRouter//​** hook+<​note>​ 
-  - Create a page at ///​dynamic-tasks//​ that is identical ​to the ///tasks// page, but it loads the **//​TasksList//​** component dynamically,​ on the client side. +This app requires JS enabled in order to work. As an optional task you can try and make it work without JS at allThis will require changing all interactions into navigations ​and form submissionsFor example the edit button for an item should take the user to the /edit/item-ids page wich displays ​the full list of todo items where the given items in the URL are editable 
-  - Build your application using **//npm run build//​** ​and then start your production-ready application using **//npm run start//**. +</note>
-  - Open the ///tasks// and ///dynamic-tasks// pages on 2 separate tabs and inspect ​the page sources for both of them. Discuss your findings with the assistant. +
-  - Create a custom 404 page (see this [[https://​nextjs.org/​docs/​advanced-features/​custom-error-page|guide]]).+
  
-<note tip>If you want to create a proper header for your application,​ [[https://​mui.com/​material-ui/​react-app-bar/​|this]] might come in handy.</​note>​ 
  
  
se/labs/04.1730462704.txt.gz · Last modified: 2024/11/01 14:05 by avner.solomon
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0