{"id":20676630,"url":"https://github.com/intermediadesigns/airbnb-clone","last_synced_at":"2026-04-24T09:34:41.391Z","repository":{"id":249209575,"uuid":"830695722","full_name":"IntermediaDesigns/airbnb-clone","owner":"IntermediaDesigns","description":"Built an Airbnb Full-Stack App with Next.js 14+, Typescript, Clerk Auth, Prisma, Supabase, Tailwind, Shadcn-ui, Zod,Vercel","archived":false,"fork":false,"pushed_at":"2024-07-19T20:40:35.000Z","size":71,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-10T18:54:05.834Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IntermediaDesigns.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-18T19:41:52.000Z","updated_at":"2024-07-19T20:40:38.000Z","dependencies_parsed_at":"2024-07-22T11:16:08.468Z","dependency_job_id":null,"html_url":"https://github.com/IntermediaDesigns/airbnb-clone","commit_stats":null,"previous_names":["intermediadesigns/airbnb-clone"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/IntermediaDesigns/airbnb-clone","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntermediaDesigns%2Fairbnb-clone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntermediaDesigns%2Fairbnb-clone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntermediaDesigns%2Fairbnb-clone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntermediaDesigns%2Fairbnb-clone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IntermediaDesigns","download_url":"https://codeload.github.com/IntermediaDesigns/airbnb-clone/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntermediaDesigns%2Fairbnb-clone/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32216972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T08:24:32.376Z","status":"ssl_error","status_checked_at":"2026-04-24T08:24:26.731Z","response_time":64,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T21:13:11.270Z","updated_at":"2026-04-24T09:34:41.366Z","avatar_url":"https://github.com/IntermediaDesigns.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Node Version\n\nThe minimum Node.js version has been bumped from 16.14 to 18.17, since 16.x has reached end-of-life.\n\n## Create Next App\n\n```sh\nnpx create-next-app@latest appName\n```\n\n## Folder Structure\n\n- app folder - where we will spend most of our time\n  - setup routes, layouts, loading states, etc\n- node_modules - project dependencies\n- public - static assets\n- .gitignore - sets which will be ignored by source control\n- bunch of config files (will discuss as we go)\n- in package.json look scripts\n- 'npm run dev' to spin up the project on http://localhost:3000\n\n```sh\nnpm run dev\n```\n\n- in globals.css remove everything after directives\n- get a hold of the README.md\n\n## Home Page\n\n- page.tsx in the root of app folder\n- represents root of our application\n  '/' local domain or production domain\n- react component (server component)\n- bunch of css classes (will discuss Tailwind in few lectures)\n- export component as default\n- file name \"page\" has a special meaning\n- snippets extension\n\n  app/page.tsx\n\n```tsx\nconst HomePage = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className='text-7xl'\u003eHomePage\u003c/h1\u003e\n    \u003c/div\u003e\n  );\n};\nexport default HomePage;\n```\n\n## Create Pages in Next.js\n\n- in the app folder create a folder with the page.js file\n  - about/page.js\n  - contact/page.js\n- can have .js .jsx .tsx extension\n\napp/about/page.tsx\n\n```tsx\nconst AboutPage = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className='text-7xl'\u003eAboutPage\u003c/h1\u003e\n    \u003c/div\u003e\n  );\n};\nexport default AboutPage;\n```\n\n## Link Component\n\n- navigate around the project\n- import Link from 'next/link'\n  home page\n\n```tsx\nimport Link from 'next/link';\nconst HomePage = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className='text-7xl'\u003eHomePage\u003c/h1\u003e\n      \u003cLink href='/about' className='text-xl text-blue-500 inline-block mt-8'\u003e\n        about page\n      \u003c/Link\u003e\n    \u003c/div\u003e\n  );\n};\nexport default HomePage;\n```\n\n## Nested Routes\n\n- app/info/contact/page.tsx\n- if no page.tsx in a segment will result in 404\n\n```tsx\nfunction ContactPage() {\n  return \u003ch1 className='text-7xl'\u003eContactPage\u003c/h1\u003e;\n}\nexport default ContactPage;\n```\n\n## CSS and Tailwind\n\n- vanilla css in globals.css\n- [Tailwind](https://tailwindcss.com/)\n\n## Layouts and Templates\n\n- layout.tsx\n- template.tsx\n\n  Layout is a component which wraps other pages and layouts. Allow to share UI. Even when the route changes, layout DOES NOT re-render. Can fetch data but can't pass it down to children. Templates are the same but they re-render.\n\n- the top-most layout is called the Root Layout. This required layout is shared across all pages in an application. Root layouts must contain html and body tags.\n- any route segment can optionally define its own Layout. These layouts will be shared across all pages in that segment.\n- layouts in a route are nested by default. Each parent layout wraps child layouts below it using the React children prop.\n\n```tsx\nimport './globals.css';\n\nexport default function RootLayout({\n  children,\n}: Readonly\u003c{\n  children: React.ReactNode;\n}\u003e) {\n  return (\n    \u003chtml lang='en'\u003e\n      \u003cbody\u003e\n        \u003cnav\u003ehello there\u003c/nav\u003e\n        {children}\n      \u003c/body\u003e\n    \u003c/html\u003e\n  );\n}\n```\n\n## Challenge - Navbar\n\n- create components/Navbar.tsx\n- render in layout.tsx\n\n```tsx\nimport Link from 'next/link';\n\nfunction Navbar() {\n  return (\n    \u003cnav className='max-w-3xl mx-auto py-4 flex gap-x-4'\u003e\n      \u003cLink href='/'\u003eHome\u003c/Link\u003e\n      \u003cLink href='/counter'\u003eCounter\u003c/Link\u003e\n      \u003cLink href='/tours'\u003eTours\u003c/Link\u003e\n      \u003cLink href='/actions'\u003eActions\u003c/Link\u003e\n    \u003c/nav\u003e\n  );\n}\nexport default Navbar;\n```\n\n```tsx\nimport Navbar from '@/components/Navbar';\n\nreturn (\n  \u003chtml lang='en'\u003e\n    \u003cbody className={inter.className}\u003e\n      \u003cNavbar /\u003e\n      \u003cmain className='max-w-3xl mx-auto py-10'\u003e{children}\u003c/main\u003e\n    \u003c/body\u003e\n  \u003c/html\u003e\n);\n```\n\n## Fonts - Google Fonts\n\nAutomatically self-host any Google Font. Fonts are included in the deployment and served from the same domain as your deployment. No requests are sent to Google by the browser.\n\n```tsx\nimport './globals.css';\nimport { Inter } from 'next/font/google';\n\nconst inter = Inter({ subsets: ['latin'] });\n\nexport default function RootLayout({\n  children,\n}: Readonly\u003c{\n  children: React.ReactNode;\n}\u003e) {\n  return (\n    \u003chtml lang='en'\u003e\n      \u003cbody className={inter.className}\u003e\n        \u003cnav\u003ehello there\u003c/nav\u003e\n        {children}\n      \u003c/body\u003e\n    \u003c/html\u003e\n  );\n}\n```\n\n## Metadata\n\nNext.js has a Metadata API that can be used to define your application metadata (e.g. meta and link tags inside your HTML head element) for improved SEO and web shareability.To define static metadata, export a Metadata object from a layout.tsx or page.tsx file.\n\n```tsx\nimport type { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n  title: 'Next.js Project',\n  description: 'A Next.js project with TypeScript and TailwindCSS.',\n  keywords: 'Next.js, Typescript, TailwindCSS',\n};\n```\n\n## Server Components VS Client Components\n\n- [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components)\n- [Client Components](https://nextjs.org/docs/app/building-your-application/rendering/client-components)\n\n- BY DEFAULT, NEXT.JS USES SERVER COMPONENTS !!!!\n- To use Client Components, you can add the React \"use client\" directive\n\n### Server Components\n\nBenefits :\n\n- data fetching\n- security\n- caching\n- bundle size\n\nData Fetching: Server Components allow you to move data fetching to the server, closer to your data source. This can improve performance by reducing time it takes to fetch data needed for rendering, and the amount of requests the client needs to make.\nSecurity: Server Components allow you to keep sensitive data and logic on the server, such as tokens and API keys, without the risk of exposing them to the client.\nCaching: By rendering on the server, the result can be cached and reused on subsequent requests and across users. This can improve performance and reduce cost by reducing the amount of rendering and data fetching done on each request.\nBundle Sizes: Server Components allow you to keep large dependencies that previously would impact the client JavaScript bundle size on the server. This is beneficial for users with slower internet or less powerful devices, as the client does not have to download, parse and execute any JavaScript for Server Components.\nInitial Page Load and First Contentful Paint (FCP): On the server, we can generate HTML to allow users to view the page immediately, without waiting for the client to download, parse and execute the JavaScript needed to render the page.\nSearch Engine Optimization and Social Network Shareability: The rendered HTML can be used by search engine bots to index your pages and social network bots to generate social card previews for your pages.\nStreaming: Server Components allow you to split the rendering work into chunks and stream them to the client as they become ready. This allows the user to see parts of the page earlier without having to wait for the entire page to be rendered on the server.\n\n### Client Components\n\nBenefits :\n\n- Interactivity: Client Components can use state, effects, and event listeners, meaning they can provide immediate feedback to the user and update the UI.\n- Browser APIs: Client Components have access to browser APIs, like geolocation or localStorage, allowing you to build UI for specific use cases.\n\n### Challenge\n\n- create counter page and setup basic counter\n\n```tsx\n'use client';\nimport { useState } from 'react';\n\nfunction Counter() {\n  const [count, setCount] = useState(0);\n  return (\n    \u003cdiv className='flex flex-col items-center w-[100px]'\u003e\n      \u003cp className='text-5xl font-bold'\u003e{count}\u003c/p\u003e\n      \u003cbutton\n        onClick={() =\u003e setCount(count + 1)}\n        className='bg-blue-500 rounded-md text-white px-4 py-2 mt-4'\n      \u003e\n        Increment\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\nexport default Counter;\n```\n\n## Challenge - Refactor\n\n- create Counter component and import in CounterPage\n- now page can be server component\n\n```tsx\nimport Counter from '@/components/Counter';\n\nfunction CounterPage() {\n  return (\n    \u003csection\u003e\n      \u003ch1 className='text-6xl mb-16'\u003ePage Content\u003c/h1\u003e\n      \u003cCounter /\u003e\n    \u003c/section\u003e\n  );\n}\nexport default CounterPage;\n```\n\n## Fetch Data in Server Components\n\n- create tours/page.tsx\n- just add async and start using await 🚀🚀🚀\n- the same for db\n- Next.tsx extends the native Web fetch() API to allow each request on the server to set its own persistent caching semantics.\n\n```tsx\nconst url = 'https://www.course-api.com/react-tours-project';\n\ntype Tour = {\n  id: string;\n  name: string;\n  info: string;\n  image: string;\n  price: string;\n};\n\nasync function ToursPage() {\n  const response = await fetch(url);\n  const data: Tour[] = await response.json();\n  console.log(data);\n  return (\n    \u003csection\u003e\n      \u003ch1 className='text-3xl mb-4'\u003eTours\u003c/h1\u003e\n\n      {data.map((tour) =\u003e {\n        return \u003ch2 key={tour.id}\u003e{tour.name}\u003c/h2\u003e;\n      })}\n    \u003c/section\u003e\n  );\n}\nexport default ToursPage;\n```\n\n## Refactor and Delay\n\n- Refresh browser on another page, navigate to tours, observe delay.\n\n```tsx\nconst fetchTours = async () =\u003e {\n  await new Promise((resolve) =\u003e setTimeout(resolve, 3000));\n  const response = await fetch(url);\n  const data: Tour[] = await response.json();\n  return data;\n};\n\nasync function ToursPage() {\n  const data = await fetchTours();\n}\n```\n\n## Loading Component\n\nThe special file loading.js helps you create meaningful Loading UI with React Suspense. With this convention, you can show an instant loading state from the server while the content of a route segment loads. The new content is automatically swapped in once rendering is complete.\n\n- tours/loading.tsx\n\n```tsx\n'use client';\nconst loading = () =\u003e {\n  return \u003cspan className='text-xl capitalize'\u003eloading tours...\u003c/span\u003e;\n};\nexport default loading;\n```\n\n## Error Component\n\nThe error.tsx file convention allows you to gracefully handle unexpected runtime errors in nested routes.\n\n- tours/error.js\n- 'use client'\n\n```js\n'use client';\nconst error = () =\u003e {\n  return \u003cdiv\u003ethere was an error...\u003c/div\u003e;\n};\nexport default error;\n```\n\n## Nested Layouts\n\n- create app/tours/layout.tsx\n- UI will be applied to app/tours - segment\n- don't forget about the \"children\"\n- we can fetch data in the layout but...\n  at the moment can't pass data down to children (pages) 😞\n\n  layout.tsx\n\n```js\nfunction ToursLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003cdiv\u003e\n      \u003cheader className='py-2 w-1/2 bg-slate-500 rounded mb-4'\u003e\n        \u003ch1 className='text-3xl text-white text-center'\u003eNested Layout\u003c/h1\u003e\n      \u003c/header\u003e\n      {children}\n    \u003c/div\u003e\n  );\n}\nexport default ToursLayout;\n```\n\n## Dynamic Routes\n\n- app/tours/[id]/page.tsx\n\n```js\nconst page = ({ params }: { params: { id: string } }) =\u003e {\n  console.log(params);\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className='text-4xl'\u003eID : {params.id}\u003c/h1\u003e\n    \u003c/div\u003e\n  );\n};\nexport default page;\n```\n\n## Challenge - Setup Links\n\n```tsx\nreturn (\n  \u003csection\u003e\n    \u003ch1 className='text-3xl mb-4'\u003eTours\u003c/h1\u003e\n\n    {data.map((tour) =\u003e {\n      return (\n        \u003cLink\n          key={tour.id}\n          href={`/tours/${tour.id}`}\n          className='hover:text-blue-500'\n        \u003e\n          \u003ch2\u003e{tour.name}\u003c/h2\u003e\n        \u003c/Link\u003e\n      );\n    })}\n  \u003c/section\u003e\n);\n```\n\n## Next Image Component\n\n- get random image from pexels site\n  [Random Image](https://www.pexels.com/photo/assorted-map-pieces-2859169/)\n\nThe Next.js Image component extends the HTML \u003cimg\u003e element with features for automatic image optimization:\n\n- Size Optimization: Automatically serve correctly sized images for each device, using modern image formats like WebP and AVIF.\n- Visual Stability: Prevent layout shift automatically when images are loading.\n- Faster Page Loads: Images are only loaded when they enter the viewport using native browser lazy loading, with optional blur-up placeholders.\n- Asset Flexibility: On-demand image resizing, even for images stored on remote servers\n\n- disable cache\n- width and height\n\n- priority property to prioritize the image for loading\n  When true, the image will be considered high priority and preload.\n\n```tsx\nimport mapsImg from '@/images/maps.jpg';\nimport Image from 'next/image';\nconst url = 'https://www.course-api.com/images/tours/tour-1.jpeg';\n\nconst page = async ({ params }: { params: { id: string } }) =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className='text-4xl'\u003eID : {params.id}\u003c/h1\u003e\n      \u003csection className='flex gap-x-4 mt-4'\u003e\n        \u003cdiv\u003e\n          \u003cImage\n            src={mapsImg}\n            alt='maps'\n            width={192}\n            height={192}\n            className='w-48 h-48 object-cover rounded'\n          /\u003e\n          \u003ch2\u003elocal image\u003c/h2\u003e\n        \u003c/div\u003e\n      \u003c/section\u003e\n    \u003c/div\u003e\n  );\n};\nexport default page;\n```\n\n## Remote Images\n\n- To use a remote image, the src property should be a URL string.\n\n- Since Next.js does not have access to remote files during the build process, you'll need to provide the width, height and optional blurDataURL props manually.\n\n- The width and height attributes are used to infer the correct aspect ratio of image and avoid layout shift from the image loading in. The width and height do not determine the rendered size of the image file.\n\n```tsx\nimport mapsImg from '@/images/maps.jpg';\nimport Image from 'next/image';\nconst url = 'https://www.course-api.com/images/tours/tour-1.jpeg';\n\nconst page = async ({ params }: { params: { id: string } }) =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className='text-4xl'\u003eID : {params.id}\u003c/h1\u003e\n      \u003csection className='flex gap-x-4 mt-4'\u003e\n        \u003cdiv\u003e\n          \u003cImage\n            src={mapsImg}\n            alt='maps'\n            width={192}\n            height={192}\n            className='w-48 h-48 object-cover rounded'\n          /\u003e\n          \u003ch2\u003elocal image\u003c/h2\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n          \u003cImage\n            src={url}\n            alt='tour'\n            width={192}\n            height={192}\n            priority\n            className='w-48 h-48 object-cover rounded'\n          /\u003e\n          \u003ch2\u003eremote image\u003c/h2\u003e\n        \u003c/div\u003e\n      \u003c/section\u003e\n    \u003c/div\u003e\n  );\n};\nexport default page;\n```\n\n```mjs\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'www.course-api.com',\n        port: '',\n        pathname: '/images/**',\n      },\n    ],\n  },\n};\n\nexport default nextConfig;\n```\n\n- To safely allow optimizing images, define a list of supported URL patterns in next.config.mjs. Be as specific as possible to prevent malicious usage.\n\n- restart dev server\n\n## Responsive Images\n\n- The fill prop allows your image to be sized by its parent element\n- sizes property helps the browser select the most appropriate image size to load based on the user's device and screen size, improving website performance and user experience.\n\nA string that provides information about how wide the image will be at different breakpoints. The value of sizes will greatly affect performance for images using fill or which are styled to have a responsive size.\n\nThe sizes property serves two important purposes related to image performance:\n\nFirst, the value of sizes is used by the browser to determine which size of the image to download, from next/image's automatically-generated source set. When the browser chooses, it does not yet know the size of the image on the page, so it selects an image that is the same size or larger than the viewport. The sizes property allows you to tell the browser that the image will actually be smaller than full screen. If you don't specify a sizes value in an image with the fill property, a default value of 100vw (full screen width) is used.\n\nSecond, the sizes property configures how next/image automatically generates an image source set. If no sizes value is present, a small source set is generated, suitable for a fixed-size image. If sizes is defined, a large source set is generated, suitable for a responsive image. If the sizes property includes sizes such as 50vw, which represent a percentage of the viewport width, then the source set is trimmed to not include any values which are too small to ever be necessary.\n\ntours.tsx\n\n```js\nreturn (\n  \u003cdiv className='grid md:grid-cols-2 gap-8'\u003e\n    {data.map((tour) =\u003e {\n      return (\n        \u003cLink\n          key={tour.id}\n          href={`/tours/${tour.id}`}\n          className='hover:text-blue-500'\n        \u003e\n          \u003cdiv className='relative h-48 mb-2'\u003e\n            \u003cImage\n              src={tour.image}\n              alt={tour.name}\n              fill\n              sizes='33vw'\n              // sizes='(max-width:768px) 100vw,(max-width:1200px) 50vw, 33vw'\n              priority\n              className='object-cover rounded'\n            /\u003e\n          \u003c/div\u003e\n          \u003ch2\u003e{tour.name}\u003c/h2\u003e\n        \u003c/Link\u003e\n      );\n    })}\n  \u003c/div\u003e\n);\n```\n\n## More Routing\n\n- Private Folders\n  \\_folder\n- Route Groups\n  (dashboard)\n- Dynamic Routes\n\n  - [...folder] - Catch-all route segment\n  - [[...folder]] Optional catch-all route segment (used by Clerk)\n\n- create test folder app/\\_css\n- create app/(dashboard)/auth\n\n  - the url is just '/auth'\n\n- create app/(dashboard)/auth/[sign-in]\n\n```ts\nconst SignInPage = ({ params }: { params: { 'sign-in': string } }) =\u003e {\n  console.log(params);\n  return \u003cdiv\u003eSignInPage\u003c/div\u003e;\n};\nexport default SignInPage;\n```\n\n- create app/(dashboard)/auth/[...sign-in]\n- create app/(dashboard)/auth/[[...sign-in]]\n\n```ts\nconst SignInPage = ({ params }: { params: { 'sign-in': string[] } }) =\u003e {\n  console.log(params);\n  console.log(params['sign-in'][1]);\n  return \u003cdiv\u003eSignInPage :{params['sign-in'][1]}\u003c/div\u003e;\n};\nexport default SignInPage;\n```\n\n## Server Actions\n\n- asynchronous server functions that can be called directly from your components.\n\n- typical setup for server state mutations (create, update, delete)\n\n  - endpoint on the server (api route on Next.js)\n  - make request from the front-end\n    - setup form, handle submission etc\n\n- Next.js server actions allow you to mutate server state directly from within a React component by defining server-side logic alongside client-side interactions.\n\nRules :\n\n- must be async\n- add 'use server' in function body (only in RSC)\n- can use in RCC but only as import\n\nRSC - React Server Component\nRCC - React Client Component\n\n```tsx\nexport default function ServerComponent() {\n  async function myAction(formData) {\n    'use server';\n    // access input values with formData\n    // formData.get('name')\n    // mutate data (server)\n    // revalidate cache\n  }\n\n  return \u003cform action={myAction}\u003e...\u003c/form\u003e;\n}\n```\n\n- or setup in a separate file ('use server' at the top)\n  - can use in both (RSC and RCC)\n\nutils/actions.js\n\n```tsx\n'use server';\n\nexport async function myAction() {\n  // ...\n}\n```\n\n```tsx\n'use client';\n\nimport { myAction } from './actions';\n\nexport default function ClientComponent() {\n  return (\n    \u003cform action={myAction}\u003e\n      \u003cbutton type='submit'\u003eAdd to Cart\u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\n```\n\n## Actions Page - Setup\n\n- create Form and UsersList in components\n\n```tsx\nimport Form from '@/components/Form';\nimport UsersList from '@/components/UsersList';\n\nfunction ActionsPage() {\n  return (\n    \u003c\u003e\n      \u003cForm /\u003e\n      \u003cUsersList /\u003e\n    \u003c/\u003e\n  );\n}\nexport default ActionsPage;\n```\n\n## Form - Setup\n\n```tsx\nconst createUser = async () =\u003e {\n  'use server';\n  console.log('creating user....');\n};\n\nfunction Form() {\n  return (\n    \u003cform action={createUser} className={formStyle}\u003e\n      \u003ch2 className='text-2xl capitalize mb-4'\u003ecreate user\u003c/h2\u003e\n      \u003cinput\n        type='text'\n        name='firstName'\n        required\n        className={inputStyle}\n        defaultValue='peter'\n      /\u003e\n      \u003cinput\n        type='text'\n        name='lastName'\n        required\n        className={inputStyle}\n        defaultValue='smith'\n      /\u003e\n      \u003cbutton type='submit' className={btnStyle}\u003e\n        submit\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\nexport default Form;\n\nconst formStyle = 'max-w-lg flex flex-col gap-y-4  shadow rounded p-8';\nconst inputStyle = 'border shadow rounded py-2 px-3 text-gray-700';\nconst btnStyle =\n  'bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded capitalize';\n```\n\n## Actions File\n\n- create utils/actions.ts\n- make \"Form\" Client Component ('use client')\n- import in Form\n\n```ts\n'use server';\n\nexport const createUser = async () =\u003e {\n  console.log('creating user....');\n};\n```\n\n```tsx\n'use client';\n\nimport { createUser } from '@/utils/actions';\n```\n\n## FormData\n\n```ts\nexport const createUser = async (formData: FormData) =\u003e {\n  const firstName = formData.get('firstName') as string;\n  const lastName = formData.get('lastName') as string;\n  const rawData = Object.fromEntries(formData);\n  console.log(rawData);\n  console.log({ firstName, lastName });\n};\n```\n\n## Save User\n\n- just as an example\n- create \"users.json\" (root !!!)\n- won't work on vercel (deployment)\n\n```ts\n'use server';\n\nimport { readFile, writeFile } from 'fs/promises';\n\ntype User = {\n  id: string;\n  firstName: string;\n  lastName: string;\n};\n\nexport const createUser = async (formData: FormData) =\u003e {\n  const firstName = formData.get('firstName') as string;\n  const lastName = formData.get('lastName') as string;\n  const newUser: User = { firstName, lastName, id: Date.now().toString() };\n  await saveUser(newUser);\n};\n\nexport const fetchUsers = async (): Promise\u003cUser[]\u003e =\u003e {\n  const result = await readFile('users.json', { encoding: 'utf8' });\n  const users = result ? JSON.parse(result) : [];\n  return users;\n};\n\nconst saveUser = async (user: User) =\u003e {\n  const users = await fetchUsers();\n  users.push(user);\n  await writeFile('users.json', JSON.stringify(users));\n};\n```\n\n## UsersList\n\n```tsx\nimport { fetchUsers } from '@/utils/actions';\nasync function UsersList() {\n  const users = await fetchUsers();\n  return (\n    \u003cdiv className='mt-4'\u003e\n      {users.length ? (\n        \u003cdiv\u003e\n          {users.map((user) =\u003e (\n            \u003ch4 key={user.id} className='capitalize text-lg'\u003e\n              {user.firstName} {user.lastName}\n            \u003c/h4\u003e\n          ))}\n        \u003c/div\u003e\n      ) : (\n        \u003cp\u003eNo users found...\u003c/p\u003e\n      )}\n    \u003c/div\u003e\n  );\n}\nexport default UsersList;\n```\n\n## RevalidatePath\n\n```ts\nimport { revalidatePath } from 'next/cache';\nimport { redirect } from 'next/navigation';\n\nexport const createUser = async (formData: FormData) =\u003e {\n  //...\n  revalidatePath('/actions');\n};\n```\n\n- if the data is displayed in a different page\n\n```ts\nexport const createUser = async (formData: FormData) =\u003e {\n  //...\n  redirect('/');\n};\n```\n\n- don't \"redirect\" place inside \"try\" block\n\n```tsx\ntry {\n  await saveUser(newUser);\n  // will trigger error\n  redirect('/');\n} catch (error) {\n  console.error(error);\n}\n```\n\n## Pending State\n\n- make sure Form is Client Component\n- in createUser switch back to revalidatePath(/actions)\n\n[React Docs - useFormStatus](https://react.dev/reference/react-dom/hooks/useFormStatus)\n\n- useFormStatus()\n- The useFormStatus Hook provides status information of the last form submission.\n\n- The useFormStatus Hook must be called from a component that is rendered inside a \u003cform\u003e.\n- useFormStatus will only return status information for a parent \u003cform\u003e.\n  It will not return status information for any \u003cform\u003e rendered in that same component or children components.\n\n  ```tsx\n  import { useFormStatus, useFormState } from 'react-dom';\n\n  const SubmitButton = () =\u003e {\n    const { pending } = useFormStatus();\n    return (\n      \u003cbutton type='submit' className={btnStyle} disabled={pending}\u003e\n        {pending ? 'submitting...' : 'submit'}\n      \u003c/button\u003e\n    );\n  };\n  ```\n\n## Result\n\n[React Docs - useFormState](https://react.dev/reference/react-dom/hooks/useFormState)\n\n- useFormState()\n- a Hook that allows you to update state based on the result of a form action.\n\n```tsx\nconst [message, formAction] = useFormState(createUser, null);\nreturn (\n  \u003cform action={formAction} className={formStyle}\u003e\n    {message \u0026\u0026 \u003cp\u003e{message}\u003c/p\u003e}\n    ...\n  \u003c/form\u003e\n);\n```\n\n```ts\nexport const createUser = async (prevState: any, formData: FormData) =\u003e {\n  // current state of the form\n  console.log(prevState);\n\n  const firstName = formData.get('firstName') as string;\n  const lastName = formData.get('lastName') as string;\n  const newUser: User = { firstName, lastName, id: Date.now().toString() };\n\n  try {\n    await saveUser(newUser);\n    revalidatePath('/actions');\n    // throw Error();\n    return 'user created successfully...';\n  } catch (error) {\n    console.error(error);\n    return 'failed to create user...';\n  }\n};\n```\n\n## Delete User\n\n- create components/DeleteButton\n- refactor UsersList\n\n```tsx\nfunction DeleteButton({ id }: { id: string }) {\n  return (\n    \u003cform\u003e\n      \u003cbutton\n        type='submit'\n        className='bg-red-500 text-white text-xs rounded p-2'\n      \u003e\n        delete\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\nexport default DeleteButton;\n```\n\n```tsx\nimport { fetchUsers } from '@/utils/actions';\nimport DeleteButton from './DeleteButton';\nasync function UsersList() {\n  const users = await fetchUsers();\n  return (\n    \u003cdiv className='mt-4'\u003e\n      {users.length ? (\n        \u003cdiv className='max-w-lg'\u003e\n          {users.map((user) =\u003e (\n            \u003ch4\n              key={user.id}\n              className='capitalize text-lg flex justify-between items-center mb-2'\n            \u003e\n              {user.firstName} {user.lastName}\n              \u003cDeleteButton id={user.id} /\u003e\n            \u003c/h4\u003e\n          ))}\n        \u003c/div\u003e\n      ) : (\n        \u003cp\u003eNo users found...\u003c/p\u003e\n      )}\n    \u003c/div\u003e\n  );\n}\nexport default UsersList;\n```\n\n## Delete Action\n\n```ts\nexport const deleteUser = async (formData: FormData) =\u003e {\n  const id = formData.get('id') as string;\n  const users = await fetchUsers();\n  const updatedUsers = users.filter((user: User) =\u003e user.id !== id);\n  await writeFile('users.json', JSON.stringify(updatedUsers));\n  revalidatePath('/actions');\n};\n```\n\n```tsx\nimport { deleteUser } from '@/utils/actions';\n\nfunction DeleteButton({ id }: { id: string }) {\n  return (\n    \u003cform action={deleteUser}\u003e\n      \u003cinput type='hidden' name='id' value={id} /\u003e\n      \u003cbutton\n        type='submit'\n        className='bg-red-500 text-white text-xs rounded p-2'\n      \u003e\n        delete\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\nexport default DeleteButton;\n```\n\n```tsx\nimport { deleteUser, removeUser } from '@/utils/actions';\n\nfunction DeleteButton({ id }: { id: string }) {\n  const removeUserWithId = removeUser.bind(null, id);\n  return (\n    \u003cform action={removeUserWithId}\u003e\n      \u003cinput type='hidden' name='name' value='shakeAndBake' /\u003e\n      \u003cbutton\n        type='submit'\n        className='bg-red-500 text-white text-xs rounded p-2'\n      \u003e\n        delete\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\nexport default DeleteButton;\n```\n\n## Bind Option\n\n- An alternative to passing arguments as hidden input fields in the form (e.g. `\u003cinput type=\"hidden\" name=\"userId\" value={userId} /\u003e`) is to use the bind option. With this approach, the value is not part of the rendered HTML and will not be encoded.\n\n- .bind works in both Server and Client Components. It also supports progressive enhancement.\n\n```ts\nexport const removeUser = async (id: string, formData: FormData) =\u003e {\n  const name = formData.get('name') as string;\n  console.log(name);\n\n  const users = await fetchUsers();\n  const updatedUsers = users.filter((user) =\u003e user.id !== id);\n  await writeFile('users.json', JSON.stringify(updatedUsers));\n  revalidatePath('/actions');\n};\n```\n\n## Route Handlers\n\n- install Thunder Client\n\nRoute Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs.\n\n- in app create folder \"api\"\n- in there create folder \"users\" with route.ts file\n\nThe following HTTP methods are supported: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. If an unsupported method is called, Next.js will return a 405 Method Not Allowed response.\n\nIn addition to supporting native Request and Response. Next.js extends them with NextRequest and NextResponse to provide convenient helpers for advanced use cases.\n\napp/api/users/route.ts\n\n```ts\n// the following HTTP methods are supported: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. If an unsupported method is called, Next.js will return a 405 Method Not Allowed response.\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { fetchUsers, saveUser } from '@/utils/actions';\n\nexport const GET = async () =\u003e {\n  const users = await fetchUsers();\n  return Response.json({ users });\n};\n\nexport const POST = async (request: Request) =\u003e {\n  const user = await request.json();\n  const newUser = { ...user, id: Date.now().toString() };\n  await saveUser(newUser);\n  return Response.json({ msg: 'user created' });\n};\n```\n\n```ts\nimport { fetchUsers, saveUser } from '@/utils/actions';\n\nexport const GET = async (request: Request) =\u003e {\n  const { searchParams } = new URL(request.url);\n  const id = searchParams.get('id');\n  console.log(id);\n\n  const users = await fetchUsers();\n  return Response.json({ users });\n};\n```\n\n```ts\nimport { fetchUsers, saveUser } from '@/utils/actions';\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport const GET = async (request: NextRequest) =\u003e {\n  console.log(request.url);\n  console.log(request.nextUrl.searchParams.get('id'));\n\n  const users = await fetchUsers();\n  return NextResponse.redirect(new URL('/', request.url));\n};\n```\n\nThe URL constructor takes two arguments: url and base. If the url is a relative URL, then base is required. If url is an absolute URL, then base is ignored.\n\nHere, '/' is the url and request.url is the base.\n\nThis means it's creating a new URL object that represents the root of the URL contained in request.url.\n\nFor example, if request.url is 'http://example.com/path/to/resource', the new URL object would represent 'http://example.com/'.\n\n## Middleware\n\n[Docs](https://nextjs.org/docs/app/building-your-application/routing/middleware)\n\nMiddleware in Next.js is a piece of code that allows you to perform actions before a request is completed and modify the response accordingly.\n\n- create middleware.ts in the root\n- by default will be invoked for every route in your project\n\n```ts\nexport function middleware(request) {\n  return Response.json({ msg: 'hello there' });\n}\n\nexport const config = {\n  matcher: '/about',\n};\n```\n\n```ts\nimport { NextResponse } from 'next/server';\n\n// This function can be marked `async` if using `await` inside\nexport function middleware(request) {\n  return NextResponse.redirect(new URL('/', request.url));\n}\n\n// See \"Matching Paths\" below to learn more\nexport const config = {\n  matcher: ['/about/:path*', '/tours/:path*'],\n};\n```\n\n## Local Build\n\n- cleanup middleware\n- fix css in UsersList.tsx\n- remove all users from 'users.json'\n- 'npm run build' followed by 'npm start'\n\n## Caching\n\n- [Vercel Video](https://www.youtube.com/watch?v=VBlSe8tvg4U)\n- [Docs](https://nextjs.org/docs/app/building-your-application/caching)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintermediadesigns%2Fairbnb-clone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintermediadesigns%2Fairbnb-clone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintermediadesigns%2Fairbnb-clone/lists"}