{"id":13456520,"url":"https://github.com/dabit3/next.js-amplify-workshop","last_synced_at":"2025-11-17T15:19:04.015Z","repository":{"id":51743048,"uuid":"310724156","full_name":"dabit3/next.js-amplify-workshop","owner":"dabit3","description":"AWS Amplify Next.js workshop","archived":false,"fork":false,"pushed_at":"2021-06-04T13:38:30.000Z","size":9174,"stargazers_count":358,"open_issues_count":7,"forks_count":84,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-10-09T09:58:59.668Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/dabit3.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}},"created_at":"2020-11-06T23:04:20.000Z","updated_at":"2025-09-04T01:17:41.000Z","dependencies_parsed_at":"2022-08-23T04:21:16.679Z","dependency_job_id":null,"html_url":"https://github.com/dabit3/next.js-amplify-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dabit3/next.js-amplify-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fnext.js-amplify-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fnext.js-amplify-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fnext.js-amplify-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fnext.js-amplify-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dabit3","download_url":"https://codeload.github.com/dabit3/next.js-amplify-workshop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fnext.js-amplify-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284905704,"owners_count":27082495,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-17T02:00:06.431Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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-07-31T08:01:23.432Z","updated_at":"2025-11-17T15:19:03.996Z","avatar_url":"https://github.com/dabit3.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Workshops"],"sub_categories":[],"readme":"# Full Stack Cloud with Next.js, Tailwind, and AWS\n\n![Next.js Amplify Workshop](images/banner.jpg)\n\nIn this workshop we'll learn how to build a full stack cloud application with [Next.js](https://nextjs.org/), [Tailwind](https://tailwindcss.com/), \u0026 [AWS Amplify](https://docs.amplify.aws/).\n\n\u003cdetails\u003e\n  \u003csummary\u003eWhat you'll be building.\u003c/summary\u003e\n\n![App preview](images/app.png)\n\n![App preview](images/app-2.png)\n\n![App preview](images/app-3.png)\n\n![App preview](images/app4.png)\n\u003c/details\u003e\n\n### Overview\n\nWe'll start from scratch, creating a new Next.js app. We'll then, step by step, use the [Amplify CLI](https://github.com/aws-amplify/amplify-cli) to build out and configure our cloud infrastructure and then use the [Amplify JS Libraries](https://github.com/aws-amplify/amplify-js) to connect the Next.js app to the APIs we create using the CLI.\n\nThe app will be a multi-user blogging platform with a markdown editor. When you think of many types of applications like Instagram, Twitter, or Facebook, they consist of a list of items and often the ability to drill down into a single item view. The app we will be building will be very similar to this, displaying a list of posts with data like the title, content, and author of the post.\n\nThis workshop should take you anywhere between 1 to 4 hours to complete.\n\n### TOC\n\n- [Getting Started](#getting-started---creating-the-nextjs-application)\n- [Adding an API](#adding-an-aws-appsync-graphql-api)\n- [Adding authentication](#adding-authentication)\n- [Enabling post creation](#adding-the-create-post-form-and-page)\n- [Adding a my-posts view](#adding-a-filtered-view-for-signed-in-users-posts)\n- [Updating and deleting posts](#updating-and-deleting-posts)\n- [Adding a cover image](#adding-a-cover-image-with-amazon-s3)\n- [Deploying to AWS](#deployment-with-amplify)\n- [Removing services](#removing-services)\n\n### Environment \u0026 prerequisites\n\nBefore we begin, make sure you have the following:\n\n- Node.js v12.x or later installed\n- A valid and confirmed AWS account\n\nWe will be working from a terminal using a [Bash shell](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) to run Amplify CLI commands to provision infrastructure and also to run a local version of the Next.js app and test it in a web browser.\n\n### Background needed / level\n\nThis workshop is intended for intermediate to advanced front end \u0026 back end developers wanting to learn more about full stack serverless development.\n\nWhile some level of React and GraphQL is helpful, this workshop requires zero previous knowledge about React or GraphQL.\n\n### Topics we'll be covering:\n\n- GraphQL API with AWS AppSync\n- Authentication\n- Authorization\n- Hosting\n- Deleting the resources\n\n## Getting Started - Creating the Next.js Application\n\nTo get started, we first need to create a new Next.js project.\n\n```bash\n$ npx create-next-app amplify-next\n```\n\nNow change into the new app directory \u0026 install AWS Amplify, AWS Amplify UI React and a few other libraries we'll be using:\n\n```bash\n$ cd amplify-next\n$ npm install aws-amplify @aws-amplify/ui-react react-simplemde-editor@4.1.5 react-markdown uuid\n```\n\nSince we will be using Tailwind, let's also install the tailwind dependencies:\n\n```sh\nnpm install tailwindcss@latest postcss@latest autoprefixer@latest @tailwindcss/typography\n```\n\nNext, create the necessary Tailwind configuration files:\n\n```sh\nnpx tailwindcss init -p\n```\n\nNow update __tailwind.config.js__ to add the Tailwind `typography` plugin to the array of plugins:\n\n```js\nplugins: [\n  require('@tailwindcss/typography')\n],\n```\n\nFinally, replace the styles in __styles/globals.css__ with the following:\n\n```css\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n```\n\n## Installing the CLI \u0026 Initializing a new AWS Amplify Project\n\n### Installing the CLI\n\nNext, we'll install the AWS Amplify CLI:\n\n```bash\n# NPM\n$ npm install -g @aws-amplify/cli\n\n# cURL (Mac \u0026 Linux)\ncurl -sL https://aws-amplify.github.io/amplify-cli/install | bash \u0026\u0026 $SHELL\n\n# cURL (Windows)\ncurl -sL https://aws-amplify.github.io/amplify-cli/install-win -o install.cmd \u0026\u0026 install.cmd\n```\n\nNow we need to configure the CLI with our credentials.\n\n\u003e If you'd like to see a video walkthrough of this configuration process, click [here](https://www.youtube.com/watch?v=fWbM5DLh25U).\n\n```sh\n$ amplify configure\n\n- Specify the AWS Region: us-east-1 || us-west-2 || eu-central-1\n- Specify the username of the new IAM user: amplify-cli-user\n\u003e In the AWS Console, click Next: Permissions, Next: Tags, Next: Review, \u0026 Create User to create the new IAM user. Then return to the command line \u0026 press Enter.\n- Enter the access key of the newly created user:   \n? accessKeyId: (\u003cYOUR_ACCESS_KEY_ID\u003e)  \n? secretAccessKey: (\u003cYOUR_SECRET_ACCESS_KEY\u003e)\n- Profile Name: amplify-cli-user\n```\n\n### Initializing A New Project\n\n```bashin\n$ amplify init\n\n- Enter a name for the project: amplifynext\n- Initialize the project with the above configuration? No\n- Enter a name for the environment: dev\n- Choose your default editor: Visual Studio Code (or your default editor)\n- Please choose the type of app that youre building: javascript\n- What javascript framework are you using: react\n- Source Directory Path: . (this sets the base directory to the root directory)\n- Distribution Directory Path: .next\n- Build Command: npm run-script build\n- Start Command: npm run-script start\n- Select the authentication method you want to use: AWS profile\n- Please choose the profile you want to use: amplify-cli-user (or your preferred profile)\n```\n\nThe Amplify CLI has initialized a new project \u0026 you will see a new folder: __amplify__ \u0026 a new file called `aws-exports.js` in the root directory. These files hold your project configuration.\n\nTo view the status of the amplify project at any time, you can run the Amplify `status` command:\n\n```sh\n$ amplify status\n```\n\nTo view the amplify project in the Amplify console at any time, run the `console` command:\n\n```sh\n$ amplify console\n```\n\n## Adding an AWS AppSync GraphQL API\n\nTo add a GraphQL API, we can use the following command:\n\n```sh\n$ amplify add api\n\n? Please select from one of the above mentioned services: GraphQL\n? Provide API name: NextBlog\n? Choose the default authorization type for the API: API key\n? Enter a description for the API key: public\n? After how many days from now the API key should expire (1-365): 365 (or your preferred expiration)\n? Do you want to configure advanced settings for the GraphQL API: No\n? Do you have an annotated GraphQL schema? N \n? Choose a schema template: Single object with fields\n? Do you want to edit the schema now? (Y/n) Y\n```\n\nThe CLI should open this GraphQL schema in your text editor.\n\n__amplify/backend/api/NextBlog/schema.graphql__\n\nUpdate the schema to the following:   \n\n```graphql\ntype Post @model {\n  id: ID!\n  title: String!\n  content: String!\n}\n```\n\nAfter saving the schema, go back to the CLI and press enter.\n\n### Deploying the API\n\nTo deploy the API, run the push command:\n\n```\n$ amplify push\n\n? Are you sure you want to continue? Y\n\n# You will be walked through the following questions for GraphQL code generation\n? Do you want to generate code for your newly created GraphQL API? Y\n? Choose the code generation language target: javascript\n? Enter the file name pattern of graphql queries, mutations and subscriptions: ./graphql/**/*.js\n? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions? Yes\n? Enter maximum statement depth [increase from default if your schema is deeply nested]: 2\n```\n\nNow the API is live and you can start interacting with it!\n\n### Testing the API\n\nTo test it out we can use the GraphiQL editor in the AppSync dashboard. To open the AppSync dashboard, run the following command:\n\n```sh\n$ amplify console api\n\n\u003e Choose GraphQL\n```\n\nIn the AppSync dashboard, click on __Queries__ to open the GraphiQL editor. In the editor, create a new post with the following mutation:\n\n```graphql\nmutation createPost {\n  createPost(input: {\n    title: \"My first post\"\n    content: \"Hello world!\"\n  }) {\n    id\n    title\n    content\n  }\n}\n```\n\nThen, query for the posts:\n\n```graphql\nquery listPosts {\n  listPosts {\n    items {\n      id\n      title\n      content\n    }\n  }\n}\n```\n\n### Configuring the Next app\n\nNow, our API is created \u0026 we can test it out in our app!\n\nThe first thing we need to do is to configure our Next.js app to be aware of our Amplify project. We can do this by referencing the auto-generated `aws-exports.js` file that was created by the CLI.\n\nCreate a new file called __configureAmplify.js__ in the root of the project and add the following code:\n\n\n```js\nimport Amplify from 'aws-amplify'\nimport config from './aws-exports'\nAmplify.configure(config)\n```\n\nNext, open __pages/\\_app.js__ and import the Amplify configuration below the last import:\n\n```js\nimport '../configureAmplify'\n```\n\nNow, our app is ready to start using our AWS services.\n\n### Interacting with the GraphQL API from the Next.js application - Querying for data\n\nNow that the GraphQL API is running we can begin interacting with it. The first thing we'll do is perform a query to fetch data from our API.\n\nTo do so, we need to define the query, execute the query, store the data in our state, then list the items in our UI.\n\nThe main thing to notice in this component is the API call. Take a look at this piece of code:\n\n```js\n/* Call API.graphql, passing in the query that we'd like to execute. */\nconst postData = await API.graphql({ query: listPosts })\n```\n\nOpen __pages/index.js__ and add the following code:\n\n```js\nimport { useState, useEffect } from 'react'\nimport Link from 'next/link'\nimport { API } from 'aws-amplify'\nimport { listPosts } from '../graphql/queries'\n\nexport default function Home() {\n  const [posts, setPosts] = useState([])\n  useEffect(() =\u003e {\n    fetchPosts()\n  }, [])\n  async function fetchPosts() {\n    const postData = await API.graphql({\n      query: listPosts\n    })\n    setPosts(postData.data.listPosts.items)\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6 mb-2\"\u003ePosts\u003c/h1\u003e\n      {\n        posts.map((post, index) =\u003e (\n        \u003cLink key={index} href={`/posts/${post.id}`}\u003e\n          \u003cdiv className=\"cursor-pointer border-b border-gray-300\tmt-8 pb-4\"\u003e\n            \u003ch2 className=\"text-xl font-semibold\"\u003e{post.title}\u003c/h2\u003e\n          \u003c/div\u003e\n        \u003c/Link\u003e)\n        )\n      }\n    \u003c/div\u003e\n  )\n}\n```\n\nNext, start the app:\n\n```sh\n$ npm run dev\n```\n\nYou should be able to view the list of posts. You will not yet be able to click on a post to navigate to the detail view, that is coming up later.\n\n## Adding authentication\n\nNext, let's add some authentication.\n\nTo add the authentication service, run the following command using the Amplify CLI:\n\n```sh\n$ amplify add auth\n\n? Do you want to use default authentication and security configuration? Default configuration \n? How do you want users to be able to sign in when using your Cognito User Pool? Username\n? Do you want to configure advanced settings? No, I am done. \n```\n\nTo deploy the authentication service, you can run the push command:\n\n```sh\n$ amplify push\n\n? Are you sure you want to continue? Yes\n```\n\nNext, let's add a profile screen and login flow to the app.\n\nTo do so, create a new file called __profile.js__ in the __pages__ directory. Here, add the following code:\n\n```js\nimport { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react'\nimport { Auth } from 'aws-amplify'\nimport { useState, useEffect } from 'react'\n\nfunction Profile() {\n  const [user, setUser] = useState(null)\n  useEffect(() =\u003e {\n    checkUser()\n  }, [])\n  async function checkUser() {\n    const user = await Auth.currentAuthenticatedUser()\n    setUser(user)\n  }\n  if (!user) return null\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6\"\u003eProfile\u003c/h1\u003e\n      \u003ch3 className=\"font-medium text-gray-500 my-2\"\u003eUsername: {user.username}\u003c/h3\u003e\n      \u003cp className=\"text-sm text-gray-500 mb-6\"\u003eEmail: {user.attributes.email}\u003c/p\u003e\n      \u003cAmplifySignOut /\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default withAuthenticator(Profile)\n```\n\nThe `withAuthenticator` Amplify UI component will scaffold out an entire authentication flow to allow users to sign up and sign in.\n\nThe `AmplifySignOut` button adds a pre-style sign out button.\n\nNext, add some styling to the UI component by opening __styles/globals.css__ and adding the following code:\n\n```css\n:root {\n  --amplify-primary-color: #2563EB;\n  --amplify-primary-tint: #2563EB;\n  --amplify-primary-shade: #2563EB;\n}\n```\n\nNext, open __pages/\\_app.js__ to add some navigation and styling to be able to navigate to the new Profile page:\n\n```js\nimport '../styles/globals.css'\nimport '../configureAmplify'\nimport Link from 'next/link'\n\nfunction MyApp({ Component, pageProps }) {\n  return (\n  \u003cdiv\u003e\n    \u003cnav className=\"p-6 border-b border-gray-300\"\u003e\n      \u003cLink href=\"/\"\u003e\n        \u003cspan className=\"mr-6 cursor-pointer\"\u003eHome\u003c/span\u003e\n      \u003c/Link\u003e\n      \u003cLink href=\"/create-post\"\u003e\n        \u003cspan className=\"mr-6 cursor-pointer\"\u003eCreate Post\u003c/span\u003e\n      \u003c/Link\u003e\n      \u003cLink href=\"/profile\"\u003e\n        \u003cspan className=\"mr-6 cursor-pointer\"\u003eProfile\u003c/span\u003e\n      \u003c/Link\u003e\n    \u003c/nav\u003e\n    \u003cdiv className=\"py-8 px-16\"\u003e\n      \u003cComponent {...pageProps} /\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  )\n}\n\nexport default MyApp\n```\n\nNext, run the app:\n\n```sh\n$ npm run dev\n```\n\nYou should now be able to sign up and view your profile.\n\n\u003e The link to __/create-post__ will not yet work as we have not yet created this page.\n\n## Adding authorization\n\nNext, update the API to enable another authorization type to enable both public and private API access.\n\n```sh\n$ amplify update api\n\n? Please select from one of the below mentioned services: GraphQL   \n? Select from the options below: Update auth settings\n? Choose the default authorization type for the API: API key\n? Enter a description for the API key: public\n? After how many days from now the API key should expire (1-365): 365 \u003cor your preferred expiration\u003e\n? Configure additional auth types? Y\n? Choose the additional authorization types you want to configure for the API: Amazon Cognito User Pool\n```\n\n![Updating the API](images/update-api.png)\n\nNext, let's update the GraphQL schema with the following changes:\n\n1. A new field (`username`) to identify the author of a post.\n2. An `@key` directive for enabling a new data access pattern to query posts by username\n\nOpen __amplify/backend/api/NextBlog/schema.graphql__ and update it with the following:\n\n```graphql\ntype Post @model\n  @key(name: \"postsByUsername\", fields: [\"username\"], queryField: \"postsByUsername\")\n  @auth(rules: [\n    { allow: owner, ownerField: \"username\" },\n    { allow: public, operations: [read] }\n  ]) {\n  id: ID!\n  title: String!\n  content: String!\n  username: String\n}\n```\n\nNext, deploy the updates:\n\n```sh\n$ amplify push --y\n```\n\nNow, you will have two types of API access:\n\n1. Private (Cognito) - to create a post, a user must be signed in. Once they have created a post, they can update and delete their own post. They can also read all posts.\n2. Public (API key) - Any user, regardless if they are signed in, can query for posts or a single post.\nUsing this combination, you can easily query for just a single user's posts or for all posts.\n\nTo make this secondary private API call from the client, the authorization type needs to be specified in the query or mutation:\n\n```js\nconst postData = await API.graphql({\n  mutation: createPost,\n  authMode: 'AMAZON_COGNITO_USER_POOLS',\n  variables: {\n    input: postInfo\n  }\n})\n```\n\n## Adding the Create Post form and page\n\nNext, create a new page at __pages/create-post.js__ and add the following code:\n\n```js\nimport { withAuthenticator } from '@aws-amplify/ui-react'\nimport { useState } from 'react'\nimport { API } from 'aws-amplify'\nimport { v4 as uuid } from 'uuid'\nimport { useRouter } from 'next/router'\nimport SimpleMDE from \"react-simplemde-editor\"\nimport \"easymde/dist/easymde.min.css\"\nimport { createPost } from '../graphql/mutations'\n\nconst initialState = { title: '', content: '' }\n\nfunction CreatePost() {\n  const [post, setPost] = useState(initialState)\n  const { title, content } = post\n  const router = useRouter()\n  function onChange(e) {\n    setPost(() =\u003e ({ ...post, [e.target.name]: e.target.value }))\n  }\n  async function createNewPost() {\n    if (!title || !content) return\n    const id = uuid()\n    post.id = id\n\n    await API.graphql({\n      query: createPost,\n      variables: { input: post },\n      authMode: \"AMAZON_COGNITO_USER_POOLS\"\n    })\n    router.push(`/posts/${id}`)\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6\"\u003eCreate new post\u003c/h1\u003e\n      \u003cinput\n        onChange={onChange}\n        name=\"title\"\n        placeholder=\"Title\"\n        value={post.title}\n        className=\"border-b pb-2 text-lg my-4 focus:outline-none w-full font-light text-gray-500 placeholder-gray-500 y-2\"\n      /\u003e \n      \u003cSimpleMDE value={post.content} onChange={value =\u003e setPost({ ...post, content: value })} /\u003e\n      \u003cbutton\n        type=\"button\"\n        className=\"mb-4 bg-blue-600 text-white font-semibold px-8 py-2 rounded-lg\"\n        onClick={createNewPost}\n      \u003eCreate Post\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default withAuthenticator(CreatePost)\n```\n\nThis will render a form and a markdown editor, allowing users to create new posts.\n\nNext, create a new folder in the __pages__ directory called __posts__ and a file called __[id].js__ within that folder. In __pages/posts/[id].js__, add the following code:\n\n```js\nimport { API } from 'aws-amplify'\nimport { useRouter } from 'next/router'\nimport ReactMarkdown from 'react-markdown'\nimport '../../configureAmplify'\nimport { listPosts, getPost } from '../../graphql/queries'\n\nexport default function Post({ post }) {\n  const router = useRouter()\n  if (router.isFallback) {\n    return \u003cdiv\u003eLoading...\u003c/div\u003e\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-5xl mt-4 font-semibold tracking-wide\"\u003e{post.title}\u003c/h1\u003e\n      \u003cp className=\"text-sm font-light my-4\"\u003eby {post.username}\u003c/p\u003e\n      \u003cdiv className=\"mt-8\"\u003e\n        \u003cReactMarkdown className='prose' children={post.content} /\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport async function getStaticPaths() {\n  const postData = await API.graphql({\n    query: listPosts\n  })\n  const paths = postData.data.listPosts.items.map(post =\u003e ({ params: { id: post.id }}))\n  return {\n    paths,\n    fallback: true\n  }\n}\n\nexport async function getStaticProps ({ params }) {\n  const { id } = params\n  const postData = await API.graphql({\n    query: getPost, variables: { id }\n  })\n  return {\n    props: {\n      post: postData.data.getPost\n    }\n  }\n}\n```\n\nThis page uses `getStaticPaths` to dynamically create pages at build time based on the posts coming back from the API.\n\nWe also use the `fallback` flag to enable fallback routes for dynamic SSG page generation.\n\n`getStaticProps` is used to enable the Post data to be passed into the page as props at build time.\n\nFinally, update __pages/index.js__ to add the author field and author styles:\n\n```js\nimport { useState, useEffect } from 'react'\nimport Link from 'next/link'\nimport { API } from 'aws-amplify'\nimport { listPosts } from '../graphql/queries'\n\nexport default function Home() {\n  const [posts, setPosts] = useState([])\n  useEffect(() =\u003e {\n    fetchPosts()\n  }, [])\n  async function fetchPosts() {\n    const postData = await API.graphql({\n      query: listPosts\n    })\n    setPosts(postData.data.listPosts.items)\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6 mb-2\"\u003ePosts\u003c/h1\u003e\n      {\n        posts.map((post, index) =\u003e (\n        \u003cLink key={index} href={`/posts/${post.id}`}\u003e\n          \u003cdiv className=\"cursor-pointer border-b border-gray-300\tmt-8 pb-4\"\u003e\n            \u003ch2 className=\"text-xl font-semibold\"\u003e{post.title}\u003c/h2\u003e\n            \u003cp className=\"text-gray-500 mt-2\"\u003eAuthor: {post.username}\u003c/p\u003e\n          \u003c/div\u003e\n        \u003c/Link\u003e)\n        )\n      }\n    \u003c/div\u003e\n  )\n}\n```\n\n### Deleting existing data\n\nNow the app is ready to test out, but before we do let's delete the existing data in the database that does not contain an author field. To do so, follow these steps:\n\n1. Open the Amplify Console\n\n```sh\n$ amplify console api\n\n\u003e Choose GraphQL\n```\n\n2. Click on __Data sources__\n3. Click on the link to the database\n4. Click on the __Items__ tab.\n5. Select the items in the database and delete them by choosing __Delete__ from the __Actions__ button.\n\nNext, run the app:\n\n```sh\n$ npm run dev\n```\n\nYou should be able to create new posts and view them dynamically.\n\n### Running a build\n\nTo run a build and test it out, run the following:\n\n```sh\n$ npm run build\n\n$ npm start\n```\n\n## Adding a filtered view for signed in user's posts\n\nIn a future step, we will be enabling the ability to edit or delete the posts that were created by the signed in user. Before we enable that functionality, let's first create a page for only viewing the posts created by the signed in user.\n\nTo do so, create a new file called __my-posts.js__ in the pages directory. This page will be using the `postsByUsername` query, passing in the username of the signed in user to query for only posts created by that user.\n\n```js\n// pages/my-posts.js\nimport { useState, useEffect } from 'react'\nimport Link from 'next/link'\nimport { API, Auth } from 'aws-amplify'\nimport { postsByUsername } from '../graphql/queries'\n\nexport default function MyPosts() {\n  const [posts, setPosts] = useState([])\n  useEffect(() =\u003e {\n    fetchPosts()\n  }, [])\n  async function fetchPosts() {\n    const { username } = await Auth.currentAuthenticatedUser()\n    const postData = await API.graphql({\n      query: postsByUsername, variables: { username }\n    })\n    setPosts(postData.data.postsByUsername.items)\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6 mb-2\"\u003eMy Posts\u003c/h1\u003e\n      {\n        posts.map((post, index) =\u003e (\n        \u003cLink key={index} href={`/posts/${post.id}`}\u003e\n          \u003cdiv className=\"cursor-pointer border-b border-gray-300\tmt-8 pb-4\"\u003e\n            \u003ch2 className=\"text-xl font-semibold\"\u003e{post.title}\u003c/h2\u003e\n            \u003cp className=\"text-gray-500 mt-2\"\u003eAuthor: {post.username}\u003c/p\u003e\n          \u003c/div\u003e\n        \u003c/Link\u003e)\n        )\n      }\n    \u003c/div\u003e\n  )\n}\n```\n\n### Updating the nav\n\nNext, we need to update the nav to show the link to the new __my-posts__ page, but only show the link if there is a signed in user.\n\nTo do so, we'll be using a combination of the `Auth` class as well as `Hub` which allows us to listen to authentication events.\n\nOpen __pages/\\_app.js__ and make the following updates:\n\n1. Import the `useState` and `useEffect` hooks from React as well as the `Auth` and `Hub` classes from AWS Amplify:\n\n```js\nimport { useState, useEffect } from 'react'\nimport { Auth, Hub } from 'aws-amplify'\n```\n\n2. In the `MyApp` function, create some state to hold the signed in user state:\n\n```js\nconst [signedInUser, setSignedInUser] = useState(false)\n```\n\n3. In the `MyApp` function, create a function to detect and maintain user state and invoke it in a `useEffect` hook:\n\n```js\nuseEffect(() =\u003e {\n  authListener()\n})\nasync function authListener() {\n  Hub.listen('auth', (data) =\u003e {\n    switch (data.payload.event) {\n      case 'signIn':\n        return setSignedInUser(true)\n      case 'signOut':\n        return setSignedInUser(false)\n    }\n  })\n  try {\n    await Auth.currentAuthenticatedUser()\n    setSignedInUser(true)\n  } catch (err) {}\n}\n```\n\n4. In the navigation, add a link to the new route to show only if a user is currently signed in:\n\n```js\n{\n  signedInUser \u0026\u0026 (\n    \u003cLink href=\"/my-posts\"\u003e\n      \u003cspan className=\"mr-6 cursor-pointer\"\u003eMy Posts\u003c/span\u003e\n    \u003c/Link\u003e\n  )\n}\n```\n\nNext, test it out by restarting the dev server:\n\n```sh\nnpm run dev\n```\n\n## Updating and deleting posts\n\nNext, let's add a way for a signed in user to edit and delete their posts.\n\nFirst, create a new folder named __edit-post__ in the __pages__ directory. Then, create a file named __[id].js__ in this folder.\n\nIn this file, we'll be accessing the `id` of the post from a route parameter. When the component loads, we will then use the post id from the route to fetch the post data and make it available for editing.\n\nIn this file, add the following code:\n\n```js\n// pages/edit-post/[id].js\nimport { useEffect, useState } from 'react'\nimport { API } from 'aws-amplify'\nimport { useRouter } from 'next/router'\nimport SimpleMDE from \"react-simplemde-editor\"\nimport \"easymde/dist/easymde.min.css\"\nimport { updatePost } from '../../graphql/mutations'\nimport { getPost } from '../../graphql/queries'\n\nfunction EditPost() {\n  const [post, setPost] = useState(null)\n  const router = useRouter()\n  const { id } = router.query\n\n  useEffect(() =\u003e {\n    fetchPost()\n    async function fetchPost() {\n      if (!id) return\n      const postData = await API.graphql({ query: getPost, variables: { id }})\n      setPost(postData.data.getPost)\n    }\n  }, [id])\n  if (!post) return null\n  function onChange(e) {\n    setPost(() =\u003e ({ ...post, [e.target.name]: e.target.value }))\n  }\n  const { title, content } = post\n  async function updateCurrentPost() {\n    if (!title || !content) return\n    await API.graphql({\n      query: updatePost,\n      variables: { input: { title, content, id } },\n      authMode: \"AMAZON_COGNITO_USER_POOLS\"\n    })\n    console.log('post successfully updated!')\n    router.push('/my-posts')\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6 mb-2\"\u003eEdit post\u003c/h1\u003e\n      \u003cinput\n        onChange={onChange}\n        name=\"title\"\n        placeholder=\"Title\"\n        value={post.title}\n        className=\"border-b pb-2 text-lg my-4 focus:outline-none w-full font-light text-gray-500 placeholder-gray-500 y-2\"\n      /\u003e \n      \u003cSimpleMDE value={post.content} onChange={value =\u003e setPost({ ...post, content: value })} /\u003e\n      \u003cbutton\n        className=\"mb-4 bg-blue-600 text-white font-semibold px-8 py-2 rounded-lg\"\n        onClick={updateCurrentPost}\u003eUpdate Post\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default EditPost       \n```\n\nNext, open __pages/my-posts.js__. We'll make a few updates to this page:\n\n1. Create a function for deleting a post\n2. Add a link to edit the post by navigating to `/edit-post/:postID`\n3. Add a link to view the post\n4. Create a button for deleting posts\n\nUpdate this file with the following code:\n\n```js\n// pages/my-posts.js\nimport { useState, useEffect } from 'react'\nimport Link from 'next/link'\nimport { API, Auth } from 'aws-amplify'\nimport { postsByUsername } from '../graphql/queries'\nimport { deletePost as deletePostMutation } from '../graphql/mutations'\n\nexport default function MyPosts() {\n  const [posts, setPosts] = useState([])\n  useEffect(() =\u003e {\n    fetchPosts()\n  }, [])\n  async function fetchPosts() {\n    const { username } = await Auth.currentAuthenticatedUser()\n    const postData = await API.graphql({\n      query: postsByUsername, variables: { username }\n    })\n    setPosts(postData.data.postsByUsername.items)\n  }\n  async function deletePost(id) {\n    await API.graphql({\n      query: deletePostMutation,\n      variables: { input: { id } },\n      authMode: \"AMAZON_COGNITO_USER_POOLS\"\n    })\n    fetchPosts()\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6 mb-2\"\u003eMy Posts\u003c/h1\u003e\n      {\n        posts.map((post, index) =\u003e (\n          \u003cdiv key={index} className=\"border-b border-gray-300\tmt-8 pb-4\"\u003e\n            \u003ch2 className=\"text-xl font-semibold\"\u003e{post.title}\u003c/h2\u003e\n            \u003cp className=\"text-gray-500 mt-2 mb-2\"\u003eAuthor: {post.username}\u003c/p\u003e\n            \u003cLink href={`/edit-post/${post.id}`}\u003e\u003ca className=\"text-sm mr-4 text-blue-500\"\u003eEdit Post\u003c/a\u003e\u003c/Link\u003e\n            \u003cLink href={`/posts/${post.id}`}\u003e\u003ca className=\"text-sm mr-4 text-blue-500\"\u003eView Post\u003c/a\u003e\u003c/Link\u003e\n            \u003cbutton\n              className=\"text-sm mr-4 text-red-500\"\n              onClick={() =\u003e deletePost(post.id)}\n            \u003eDelete Post\u003c/button\u003e\n          \u003c/div\u003e\n        ))\n      }\n    \u003c/div\u003e\n  )\n}\n```\n\n### Enabling Incremental Static Generation\n\nThe last thing we need to do is implement [Incremental Static Generation](https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration). Since we are allowing users to update posts, we need to have a way for our site to render the newly updated posts. \n\nIncremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in.\n\nTo enable this, open __pages/posts/[id].js__ and update the `getStaticProps` method with the following:\n\n```js\n\nexport async function getStaticProps ({ params }) {\n  const { id } = params\n  const postData = await API.graphql({\n    query: getPost, variables: { id }\n  })\n  return {\n    props: {\n      post: postData.data.getPost\n    },\n    // Next.js will attempt to re-generate the page:\n    // - When a request comes in\n    // - At most once every second\n    revalidate: 1 // adds Incremental Static Generation, sets time in seconds\n  }\n}\n```\n\nTo test it out, restart the server or run a new build:\n\n```sh\nnpm run dev\n\n# or\n\nnpm run build \u0026\u0026 npm start\n```\n\n## Adding a cover image with Amazon S3\n\nNext, let's give users the ability to add a cover image to their post.\n\nTo do so, we need to do the following things:\n\n1. Add the `storage` category to the Amplify project.\n2. Update the GraphQL schema to add a `coverImage` field to the `Post` type\n3. Update the UI to enable users to upload images\n4. Update the UI to render the cover image (if it exists)\n\nTo get started, let's first open the GraphQL schema located at __amplify/backend/api/NextBlog/schema.graphql__ and add a `coverImage` field:\n\n```graphql\ntype Post @model\n  @key(name: \"postsByUsername\", fields: [\"username\"], queryField: \"postsByUsername\")\n  @auth(rules: [\n    { allow: owner, ownerField: \"username\" },\n    { allow: public, operations: [read] }\n  ]) {\n  id: ID!\n  title: String!\n  content: String!\n  username: String\n  coverImage: String\n}\n```\n\nNext, enable file storage by running the Amplify `add` command:\n\n```sh\namplify add storage\n\n? Please select from one of the below mentioned services: Content (Images, audio, video, etc.)\n? Please provide a friendly name for your resource that will be used to label this category in the project: projectimages\n? Please provide bucket name: \u003cyour-globally-unique-bucket-name\u003e\n? Who should have access: Auth and guest users\n? What kind of access do you want for Authenticated users? create/update, read, delete\n? What kind of access do you want for Guest users? read\n? Do you want to add a Lambda Trigger for your S3 Bucket? No\n```\n\nNext, deploy the back end:\n\n```sh\namplify push --y\n```\n\n### Allowing users to upload a cover image\n\nNext, let's enable the ability to upload a cover image when creating a post.\n\nTo do so, open __pages/create-post.js__.\n\nWe will be making the following updates.\n\n1. Adding a button to enable users to upload a file and save it in the local state\n2. Import the Amplify `Storage` category and `useRef` from React.\n3. When creating a new post, we will check to see if there is an image in the local state, and if there is then upload the image to S3 and store the image key along with the other post data.\n4. When a user uploads an image, show a preview of the image in the UI\n\n```js\n// pages/create-post.js.\nimport { withAuthenticator } from '@aws-amplify/ui-react'\nimport { useState, useRef } from 'react' // new\nimport { API, Storage } from 'aws-amplify'\nimport { v4 as uuid } from 'uuid'\nimport { useRouter } from 'next/router'\nimport SimpleMDE from \"react-simplemde-editor\"\nimport \"easymde/dist/easymde.min.css\"\nimport { createPost } from '../graphql/mutations'\n\nconst initialState = { title: '', content: '' }\n\nfunction CreatePost() {\n  const [post, setPost] = useState(initialState)\n  const [image, setImage] = useState(null)\n  const hiddenFileInput = useRef(null);\n  const { title, content } = post\n  const router = useRouter()\n  function onChange(e) {\n    setPost(() =\u003e ({ ...post, [e.target.name]: e.target.value }))\n  }\n  async function createNewPost() {\n    if (!title || !content) return\n    const id = uuid() \n    post.id = id\n    // If there is an image uploaded, store it in S3 and add it to the post metadata\n    if (image) {\n      const fileName = `${image.name}_${uuid()}`\n      post.coverImage = fileName\n      await Storage.put(fileName, image)\n    }\n\n    await API.graphql({\n      query: createPost,\n      variables: { input: post },\n      authMode: \"AMAZON_COGNITO_USER_POOLS\"\n    })\n    router.push(`/posts/${id}`)\n  }\n  async function uploadImage() {\n    hiddenFileInput.current.click();\n  }\n  function handleChange (e) {\n    const fileUploaded = e.target.files[0];\n    if (!fileUploaded) return\n    setImage(fileUploaded)\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6\"\u003eCreate new post\u003c/h1\u003e\n      \u003cinput\n        onChange={onChange}\n        name=\"title\"\n        placeholder=\"Title\"\n        value={post.title}\n        className=\"border-b pb-2 text-lg my-4 focus:outline-none w-full font-light text-gray-500 placeholder-gray-500 y-2\"\n      /\u003e \n      {\n        image \u0026\u0026 (\n          \u003cimg src={URL.createObjectURL(image)} className=\"my-4\" /\u003e\n        )\n      }\n      \u003cSimpleMDE value={post.content} onChange={value =\u003e setPost({ ...post, content: value })} /\u003e\n      \u003cinput\n        type=\"file\"\n        ref={hiddenFileInput}\n        className=\"absolute w-0 h-0\"\n        onChange={handleChange}\n      /\u003e\n      \u003cbutton\n        className=\"bg-purple-600 text-white font-semibold px-8 py-2 rounded-lg mr-2\" \n        onClick={uploadImage}        \n      \u003e\n        Upload Cover Image\n      \u003c/button\u003e\n      \u003cbutton\n        type=\"button\"\n        className=\"mb-4 bg-blue-600 text-white font-semibold px-8 py-2 rounded-lg\"\n        onClick={createNewPost}\n      \u003eCreate Post\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default withAuthenticator(CreatePost)\n```\n\nNow, users should be able to upload a cover image along with their post. If there is a cover image present, it will show them a preview.\n\n### Rendering the cover image in the detail view\n\nNext, let's look at how to render the cover image. To do so, we need to check to see if the cover image key exists as part of the post. If it does, we will fetch the image from S3 and render it in the view.\n\nUpdate __pages/posts/[id].js__ with the following:\n\n```js\n// pages/posts/[id].js \nimport { API, Storage } from 'aws-amplify'\nimport { useState, useEffect } from 'react'\nimport { useRouter } from 'next/router'\nimport ReactMarkdown from 'react-markdown'\nimport { listPosts, getPost } from '../../graphql/queries'\n\nexport default function Post({ post }) {\n  const [coverImage, setCoverImage] = useState(null)\n  useEffect(() =\u003e {\n    updateCoverImage()\n  }, [])\n  async function updateCoverImage() {\n    if (post.coverImage) {\n      const imageKey = await Storage.get(post.coverImage)\n      setCoverImage(imageKey)\n    }\n  }\n  console.log('post: ', post)\n  const router = useRouter()\n  if (router.isFallback) {\n    return \u003cdiv\u003eLoading...\u003c/div\u003e\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-5xl mt-4 font-semibold tracking-wide\"\u003e{post.title}\u003c/h1\u003e\n      {\n        coverImage \u0026\u0026 \u003cimg src={coverImage} className=\"mt-4\" /\u003e\n      }\n      \u003cp className=\"text-sm font-light my-4\"\u003eby {post.username}\u003c/p\u003e\n      \u003cdiv className=\"mt-8\"\u003e\n        \u003cReactMarkdown className='prose' children={post.content} /\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport async function getStaticPaths() {\n  const postData = await API.graphql({\n    query: listPosts\n  })\n  const paths = postData.data.listPosts.items.map(post =\u003e ({ params: { id: post.id }}))\n  return {\n    paths,\n    fallback: true\n  }\n}\n\nexport async function getStaticProps ({ params }) {\n  const { id } = params\n  const postData = await API.graphql({\n    query: getPost, variables: { id }\n  })\n  return {\n    props: {\n      post: postData.data.getPost\n    }\n  }\n}\n```\n\n### Allowing users the ability to update a cover image\n\nNext, let's enable users to edit a post that contains a cover image. To do so, we'll need to enable similar functionality as we did when allowing users to create a post with a cover image.\n\nWe'll need to detect whether a post has a cover image, but also whether they have uploaded a new cover image and save the update if they have done so.\n\nTo implement this, update __pages/edit-post/[id].js__ with the following code:\n\n```js\n// pages/edit-post/[id].js\nimport { useEffect, useState, useRef } from 'react'\nimport { API, Storage } from 'aws-amplify'\nimport { useRouter } from 'next/router'\nimport SimpleMDE from \"react-simplemde-editor\"\nimport \"easymde/dist/easymde.min.css\"\nimport { v4 as uuid } from 'uuid'\nimport { updatePost } from '../../graphql/mutations'\nimport { getPost } from '../../graphql/queries'\n\nfunction EditPost() {\n  const [post, setPost] = useState(null)\n  const router = useRouter()\n  const { id } = router.query\n  const [coverImage, setCoverImage] = useState(null)\n  const [localImage, setLocalImage] = useState(null)\n  const fileInput = useRef(null)\n\n  useEffect(() =\u003e {\n    fetchPost()\n    async function fetchPost() {\n      if (!id) return\n      const postData = await API.graphql({ query: getPost, variables: { id }})\n      console.log('postData: ', postData)\n      setPost(postData.data.getPost)\n      if (postData.data.getPost.coverImage) {\n        updateCoverImage(postData.data.getPost.coverImage)\n      }\n    }\n  }, [id])\n  if (!post) return null\n  async function updateCoverImage(coverImage) {\n    const imageKey = await Storage.get(coverImage)\n    setCoverImage(imageKey)\n  }\n  async function uploadImage() {\n    fileInput.current.click();\n  }\n  function handleChange (e) {\n    const fileUploaded = e.target.files[0];\n    if (!fileUploaded) return\n    setCoverImage(fileUploaded)\n    setLocalImage(URL.createObjectURL(fileUploaded))\n  }\n  function onChange(e) {\n    setPost(() =\u003e ({ ...post, [e.target.name]: e.target.value }))\n  }\n  const { title, content } = post\n  async function updateCurrentPost() {\n    if (!title || !content) return\n    const postUpdated = {\n      id, content, title\n    }\n    // check to see if there is a cover image and that it has been updated\n    if (coverImage \u0026\u0026 localImage) {\n      const fileName = `${coverImage.name}_${uuid()}` \n      postUpdated.coverImage = fileName\n      await Storage.put(fileName, coverImage)\n    }\n    await API.graphql({\n      query: updatePost,\n      variables: { input: postUpdated },\n      authMode: \"AMAZON_COGNITO_USER_POOLS\"\n    })\n    console.log('post successfully updated!')\n    router.push('/my-posts')\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6 mb-2\"\u003eEdit post\u003c/h1\u003e\n      {\n        coverImage \u0026\u0026 \u003cimg src={localImage ? localImage : coverImage} className=\"mt-4\" /\u003e\n      }\n      \u003cinput\n        onChange={onChange}\n        name=\"title\"\n        placeholder=\"Title\"\n        value={post.title}\n        className=\"border-b pb-2 text-lg my-4 focus:outline-none w-full font-light text-gray-500 placeholder-gray-500 y-2\"\n      /\u003e \n      \u003cSimpleMDE value={post.content} onChange={value =\u003e setPost({ ...post, content: value })} /\u003e\n      \u003cinput\n        type=\"file\"\n        ref={fileInput}\n        className=\"absolute w-0 h-0\"\n        onChange={handleChange}\n      /\u003e\n      \u003cbutton\n        className=\"bg-purple-600 text-white font-semibold px-8 py-2 rounded-lg mr-2\" \n        onClick={uploadImage}        \n      \u003e\n        Upload Cover Image\n      \u003c/button\u003e\n      \u003cbutton\n        className=\"mb-4 bg-blue-600 text-white font-semibold px-8 py-2 rounded-lg\"\n        onClick={updateCurrentPost}\u003eUpdate Post\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default EditPost \n```\n\nNow, users should be able to edit the cover image if it exists, or add a cover image for posts that do not contain one.\n\n### Rendering a cover image thumbnail preview\n\nThe last thing we may want to do is give a preview of the cover image in the list of posts on the main index page.\n\nTo do so, let's update our code to see if there is a cover image associated with each post. If there is, we'll fetch the image from S3 and then render the post image if it exists.\n\nTo implement this, open __pages/index.js__ and update it with the following code:\n\n```js\n// pages/index.js\nimport { useState, useEffect } from 'react'\nimport Link from 'next/link'\nimport { API, Storage } from 'aws-amplify'\nimport { listPosts } from '../graphql/queries'\n\nexport default function Home() {\n  const [posts, setPosts] = useState([])\n  useEffect(() =\u003e {\n    fetchPosts()\n  }, [])\n  async function fetchPosts() {\n    const postData = await API.graphql({\n      query: listPosts\n    })\n    const { items } = postData.data.listPosts\n    // Fetch images from S3 for posts that contain a cover image\n    const postsWithImages = await Promise.all(items.map(async post =\u003e {\n      if (post.coverImage) {\n        post.coverImage = await Storage.get(post.coverImage)\n      }\n      return post\n    }))\n    setPosts(postsWithImages)\n  }\n  return (\n    \u003cdiv\u003e\n      \u003ch1 className=\"text-3xl font-semibold tracking-wide mt-6 mb-8\"\u003ePosts\u003c/h1\u003e\n      {\n        posts.map((post, index) =\u003e (\n        \u003cLink key={index} href={`/posts/${post.id}`}\u003e\n          \u003cdiv className=\"my-6 pb-6 border-b border-gray-300\t\"\u003e\n            {\n              post.coverImage \u0026\u0026 \u003cimg src={post.coverImage} className=\"w-56\" /\u003e\n            }\n            \u003cdiv className=\"cursor-pointer mt-2\"\u003e\n              \u003ch2 className=\"text-xl font-semibold\"\u003e{post.title}\u003c/h2\u003e\n              \u003cp className=\"text-gray-500 mt-2\"\u003eAuthor: {post.username}\u003c/p\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/Link\u003e)\n        )\n      }\n    \u003c/div\u003e\n  )\n}\n```\n\nIf you'd like to also have the same functionality to preview cover images in the __my-posts.js__ view, try adding the same updates there.\n\n## Deployment with amplify\n\nTo deploy to AWS hosting, follow the guide laid out [here](https://docs.amplify.aws/guides/hosting/nextjs/q/platform/js)\n\n## Removing Services\n\nIf at any time, or at the end of this workshop, you would like to delete a service from your project \u0026 your account, you can do this by running the `amplify remove` command:\n\n```sh\n$ amplify remove auth\n\n$ amplify push\n```\n\nIf you are unsure of what services you have enabled at any time, you can run the `amplify status` command:\n\n```sh\n$ amplify status\n```\n\n`amplify status` will give you the list of resources that are currently enabled in your app.\n\n### Deleting the Amplify project and all services\n\nIf you'd like to delete the entire project, you can run the `delete` command:\n\n```sh\n$ amplify delete\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabit3%2Fnext.js-amplify-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdabit3%2Fnext.js-amplify-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabit3%2Fnext.js-amplify-workshop/lists"}