├── .gitignore ├── README.md ├── components ├── ContextMenu.js ├── Footer.js ├── Header.js ├── Hero.js ├── Nav.js ├── Post.js └── PostCard.js ├── fixtures.js ├── hooks └── useUser.js ├── jsconfig.json ├── package.json ├── pages ├── _app.js ├── api │ └── preview.js ├── edit │ ├── [slug].js │ └── new.js ├── index.js ├── login.js └── posts │ └── [slug].js ├── postcss.config.js ├── screenshot.png ├── slides.key ├── styles └── index.css ├── tailwind.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # From Front-end to Full Stack – Next.js Conf 2020 2 | 3 | > This project demonstrates the architecture changes Amplify JS needed to support Next.js, 4 | > authentication & data-access in a client-side application, 5 | > and how to move logic to the server for better performance. 6 | 7 | ![Screenshot of app](screenshot.png) 8 | 9 | ## Setup 10 | 11 | 1. Clone this repo 12 | 13 | ```shell 14 | git clone git@github.com:ericclemmons/next.js-conf-2020.git 15 | ``` 16 | 17 | 1. Change into the directory & install the dependencies 18 | 19 | ```shell 20 | cd next.js-conf-2020 21 | 22 | yarn install 23 | ``` 24 | 25 | 1. **Next, choose your own adventure:**: 26 | 27 | 1. You can either continue with the [`main`](https://github.com/ericclemmons/next.js-conf-2020/tree/main) branch & setup Amplify from scratch (e.g. `amplify init`, `amplify add auth`, `amplify add api`). 28 | 1. Or, you can checkout the [`live-demo`](https://github.com/ericclemmons/next.js-conf-2020/tree/live-demo) branch and re-use the backend configuration from the presentation. 29 | 30 | ## New Backend ([`main`](https://github.com/ericclemmons/next.js-conf-2020/tree/main)) 31 | 32 | 1. Initialize Amplify 33 | 34 | ```shell 35 | amplify init 36 | ``` 37 | 38 |
39 | 40 | More details… 41 | 42 | 43 | ```console 44 | $ amplify init 45 | ? Enter a name for the project nextjsconf2020 46 | ? Enter a name for the environment dev 47 | ? Choose your default editor: Visual Studio Code 48 | ? Choose the type of app that you're building javascript 49 | Please tell us about your project 50 | ? What javascript framework are you using react 51 | ? Source Directory Path: src 52 | ? Distribution Directory Path: build 53 | ? Build Command: npm run-script build 54 | ? Start Command: npm run-script start 55 | ``` 56 | 57 |
58 | 59 | 1. Add Authentication 60 | 61 | ```shell 62 | amplify add auth 63 | ``` 64 | 65 |
66 | 67 | More details… 68 | 69 | 70 | ```console 71 | $ amplify add auth 72 | Do you want to use the default authentication and security configuration? Default configuration 73 | Warning: you will not be able to edit these selections. 74 | How do you want users to be able to sign in? Username 75 | Do you want to configure advanced settings? No, I am done. 76 | ``` 77 | 78 |
79 | 80 | 1. Add API 81 | 82 | ```shell 83 | amplify add api 84 | ``` 85 | 86 |
87 | 88 | More details… 89 | 90 | 91 | ```console 92 | $ amplify add api 93 | ? Please select from one of the below mentioned services: GraphQL 94 | ? Provide API name: nextjsconf2020 95 | ? Choose the default authorization type for the API API key 96 | ? Enter a description for the API key: 97 | ? After how many days from now the API key should expire (1-365): 7 98 | ? Do you want to configure advanced settings for the GraphQL API Yes, I want to make some additional changes. 99 | ? Configure additional auth types? Yes 100 | ? Choose the additional authorization types you want to configure for the API Amazon Cognito User Pool 101 | Cognito UserPool configuration 102 | Use a Cognito user pool configured as a part of this project. 103 | ? Configure conflict detection? No 104 | ? Do you have an annotated GraphQL schema? No 105 | ? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description) 106 | ``` 107 | 108 |
109 | 110 | Next, you'll be prompted to edit your schema: 111 | 112 | > ? Do you want to edit the schema now? **Yes** 113 | 114 | Replace the contents of your editor with the following, then save: 115 | 116 | ```gql 117 | type Post 118 | @model 119 | # See: https://docs.amplify.aws/cli/graphql-transformer/directives#owner-authorization 120 | @auth(rules: [{ allow: owner }, { allow: public, operations: [read] }]) 121 | # See: https://docs.amplify.aws/cli/graphql-transformer/directives#how-to-use-key 122 | @key(name: "postsBySlug", fields: ["slug"], queryField: "postsBySlug") { 123 | id: ID! 124 | title: String! 125 | slug: String! 126 | tags: [String!]! 127 | snippet: String! 128 | content: String! 129 | published: Boolean! 130 | } 131 | ``` 132 | 133 | 1. Deploy your backend to the cloud 134 | 135 | ```shell 136 | amplify push 137 | ``` 138 | 139 |
140 | 141 | More details… 142 | 143 | 144 | ```console 145 | ? Do you want to generate code for your newly created GraphQL API Yes 146 | ? Choose the code generation language target javascript 147 | ? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js 148 | ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes 149 | ? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 150 | ``` 151 | 152 |
153 | 154 | 1. Run the app locally 155 | 156 | ```shell 157 | yarn dev 158 | ``` 159 | 160 | Visit http://localhost:3000/ to view the app! 161 | -------------------------------------------------------------------------------- /components/ContextMenu.js: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from "@headlessui/react"; 2 | import { useUser } from "hooks/useUser"; 3 | 4 | export function ContextMenu({ children = null }) { 5 | const user = useUser(); 6 | 7 | if (!user) { 8 | return ( 9 |
10 | 11 | 15 | Log In 16 | 17 | 18 |
19 | ); 20 | } 21 | 22 | return ( 23 |
24 | 25 | {({ open }) => ( 26 | <> 27 | 28 | 29 | Options 30 | 35 | 40 | 41 | 42 | 43 | 44 | 53 | 57 |
58 |

Signed in as

59 |

60 | {user.attributes.email} 61 |

62 |
63 | 64 |
65 | 66 | {({ active }) => ( 67 | 73 | 79 | 84 | 85 | Create new post 86 | 87 | )} 88 | 89 | 90 | {children} 91 |
92 | 93 |
94 | 95 | {({ active }) => ( 96 |
101 | {/* TODO Use AmplifySignOut */} 102 | 103 |
104 | )} 105 |
106 |
107 |
108 |
109 | 110 | )} 111 |
112 |
113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | export function Footer() { 2 | return ( 3 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import { Nav } from "components/Nav"; 2 | 3 | export function Header({ children }) { 4 | return ( 5 |
6 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /components/Hero.js: -------------------------------------------------------------------------------- 1 | import { Header } from "components/Header"; 2 | 3 | export function Hero({ children }) { 4 | return ( 5 |
6 |
7 |
8 | 15 | 16 | 24 | 32 | 33 | 34 | 39 | 40 | 47 | 48 | 56 | 64 | 65 | 66 | 71 | 72 |
73 |
74 | 75 |
{children}
76 |
77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /components/Nav.js: -------------------------------------------------------------------------------- 1 | export function Nav() { 2 | return ( 3 |
4 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/Post.js: -------------------------------------------------------------------------------- 1 | import { Header } from "components/Header"; 2 | import ReactMarkdown from "react-markdown"; 3 | 4 | export function Post({ post }) { 5 | return ( 6 | <> 7 |
8 |
9 | 16 | 17 | 25 | 33 | 34 | 35 | 40 | 41 | 48 | 49 | 57 | 65 | 66 | 67 | 72 | 73 | 80 | 81 | 89 | 97 | 98 | 99 | 104 | 105 |
106 |
107 | 108 |
109 |
110 |
111 | {post.tags.map((tag) => ( 112 |

116 | {tag} 117 |

118 | ))} 119 |

120 | {post.title} 121 |

122 |

123 | {post.snippet} 124 |

125 |
126 |
127 |
128 | 129 |
130 |
131 |
132 | 139 |
140 | 141 | {post.content} 142 |
143 |
144 | 145 | ); 146 | } 147 | -------------------------------------------------------------------------------- /components/PostCard.js: -------------------------------------------------------------------------------- 1 | export function PostCard({ post }) { 2 | return ( 3 |
7 |
8 | {!post.published && ( 9 |

10 | 16 | 21 | 22 | Draft 23 |

24 | )} 25 | 26 | 31 |
32 |
33 |
34 |

35 | 36 | {post.tags.join(", ")} 37 | 38 |

39 | 47 |

48 | {post.title} 49 |

50 |

51 | {post.snippet} 52 |

53 |
54 |
55 |
56 |
57 | 64 | · 65 | 66 | {Math.ceil(post.content.split(" ").length / 200)} min read 67 | 68 |
69 |
70 |
71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /fixtures.js: -------------------------------------------------------------------------------- 1 | exports.posts = [ 2 | { 3 | id: "abc1", 4 | content: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`.repeat( 5 | 50 6 | ), 7 | createdAt: "2020-03-11T00:00:00.000Z", 8 | published: false, 9 | slug: "boost-your-conversion-rate", 10 | snippet: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`, 11 | tags: ["blog"], 12 | title: "Placeholder for Boost your conversion rate", 13 | updatedAt: "2020-03-11T00:00:00.000Z", 14 | }, 15 | { 16 | id: "abc2", 17 | content: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit facilis asperiores porro quaerat doloribus, eveniet dolore. Adipisci tempora aut inventore optio animi., tempore temporibus quo laudantium.`.repeat( 18 | 50 19 | ), 20 | createdAt: "2020-05-19T22:56:05.236Z", 21 | slug: "how-to-use-search-engine-optimization-to-drive-sales", 22 | snippet: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit facilis asperiores porro quaerat doloribus, eveniet dolore. Adipisci tempora aut inventore optio animi., tempore temporibus quo laudantium.`, 23 | published: true, 24 | tags: ["video"], 25 | title: 26 | "Placeholder for How to use search engine optimization to drive sales", 27 | updatedAt: "2020-05-19T22:56:05.236Z", 28 | }, 29 | { 30 | id: "abc3", 31 | createdAt: "2020-10-24T22:56:05.236Z", 32 | content: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`.repeat( 33 | 50 34 | ), 35 | published: true, 36 | slug: "improve-your-customer-experience", 37 | snippet: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`, 38 | tags: ["case study"], 39 | title: "Placeholder for Improve your customer experience", 40 | updatedAt: "2020-10-24T22:56:05.236Z", 41 | }, 42 | ]; 43 | 44 | exports.createPost = { 45 | data: { 46 | createPost: exports.posts[0], 47 | }, 48 | }; 49 | 50 | exports.updatePost = { 51 | data: { 52 | updatePost: exports.posts[0], 53 | }, 54 | }; 55 | 56 | exports.user = { 57 | username: "ericclemmons", 58 | attributes: { 59 | email: "eric@smarterspam.com", 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /hooks/useUser.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export function useUser() { 4 | const [user, setUser] = useState(null); 5 | 6 | useEffect(() => { 7 | // TODO Fetch `Auth.currentAuthenticatedUser` 8 | Promise.resolve(require("fixtures").user).then(setUser).catch(console.warn); 9 | 10 | // TODO Listen to `Hub` for `auth`'s { payload } for: 11 | // - `signIn` `event` (with `data`) 12 | // - `signOut` `event` 13 | }, []); 14 | 15 | return user; 16 | } 17 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "." 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-tailwindcss", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "next", 6 | "build": "next build", 7 | "start": "next start" 8 | }, 9 | "dependencies": { 10 | "@headlessui/react": "^0.2.0", 11 | "@tailwindcss/ui": "^0.6.2", 12 | "lodash": "^4.17.20", 13 | "next": "latest", 14 | "react": "^16.13.1", 15 | "react-dom": "^16.13.1", 16 | "react-markdown": "^4.3.1" 17 | }, 18 | "devDependencies": { 19 | "postcss-flexbugs-fixes": "4.2.1", 20 | "postcss-preset-env": "^6.7.0", 21 | "tailwindcss": "^1.9.4" 22 | }, 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import { Footer } from "components/Footer"; 2 | import Head from "next/head"; 3 | import "../styles/index.css"; 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 | <> 8 | 9 | Next.js + Ampify Blog 10 | 11 | 12 | 13 | 14 |