├── README.md ├── public ├── favicon.ico └── images │ └── profile.jpg ├── pages ├── api │ └── hello.js ├── _app.js ├── posts │ ├── first-post.js │ └── [id].js └── index.js ├── components ├── date.js ├── layout.module.css └── layout.js ├── package.json ├── .gitignore ├── posts ├── pre-rendering.md └── ssg-ssr.md ├── styles ├── globals.css ├── utils.module.css └── Home.module.css └── lib └── posts.js /README.md: -------------------------------------------------------------------------------- 1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn). -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/nextjs-blog/main/public/favicon.ico -------------------------------------------------------------------------------- /public/images/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/nextjs-blog/main/public/images/profile.jpg -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | export default function handler(req, res) { 2 | res.status(200).json({ text: 'Hello' }); 3 | } 4 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | 3 | export default function App({ Component, pageProps }) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /components/date.js: -------------------------------------------------------------------------------- 1 | import { parseISO, format } from 'date-fns'; 2 | 3 | export default function Date({ dateString }) { 4 | const date = parseISO(dateString); 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /components/layout.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 36rem; 3 | padding: 0 1rem; 4 | margin: 3rem auto 6rem; 5 | } 6 | 7 | .header { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | } 12 | 13 | .backToHome { 14 | margin: 3rem 0 0; 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "date-fns": "^2.29.3", 10 | "gray-matter": "^4.0.3", 11 | "next": "latest", 12 | "react": "18.2.0", 13 | "react-dom": "18.2.0", 14 | "remark": "^14.0.2", 15 | "remark-html": "^15.0.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pages/posts/first-post.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import Head from 'next/head'; 3 | import Layout from '../../components/layout'; 4 | 5 | export default function FirstPost() { 6 | return ( 7 | 8 | 9 | First Post 10 | 11 |

First Post

12 |

13 | Back to home 14 |

15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /.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 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /posts/pre-rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Two Forms of Pre-rendering' 3 | date: '2020-01-01' 4 | --- 5 | 6 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page. 7 | 8 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request. 9 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**. 10 | 11 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others. -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, 6 | Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | line-height: 1.6; 8 | font-size: 18px; 9 | } 10 | 11 | a { 12 | color: #0070f3; 13 | text-decoration: none; 14 | } 15 | 16 | a:hover { 17 | text-decoration: underline; 18 | } 19 | 20 | * { 21 | box-sizing: border-box; 22 | } 23 | 24 | img { 25 | max-width: 100%; 26 | display: block; 27 | } 28 | 29 | /* h1, 30 | h2, 31 | p, 32 | ul { 33 | margin: 0; 34 | } 35 | 36 | ul { 37 | padding: 0; 38 | list-style: none; 39 | } 40 | 41 | button { 42 | padding: 0.5rem 1rem; 43 | font-weight: bold; 44 | } */ 45 | -------------------------------------------------------------------------------- /styles/utils.module.css: -------------------------------------------------------------------------------- 1 | .heading2x1 { 2 | font-size: 2.5rem; 3 | line-height: 1.2; 4 | font-weight: 800; 5 | letter-spacing: -0.05rem; 6 | margin: 1rem 0; 7 | } 8 | 9 | .headingX1 { 10 | font-size: 2rem; 11 | line-height: 1.3; 12 | font-weight: 800; 13 | letter-spacing: -0.05rem; 14 | margin: 1rem 0; 15 | } 16 | 17 | .headingLg { 18 | font-size: 1.5rem; 19 | line-height: 1.4; 20 | margin: 1rem 0; 21 | } 22 | 23 | .headingMd { 24 | font-size: 1.2rem; 25 | line-height: 1.5; 26 | } 27 | 28 | .borderCircle { 29 | border-radius: 9999px; 30 | } 31 | 32 | .colorInherit { 33 | color: inherit; 34 | } 35 | 36 | .padding1px { 37 | padding-top: 1px; 38 | } 39 | 40 | .list { 41 | list-style: none; 42 | padding: 0; 43 | margin: 0; 44 | } 45 | 46 | .listItem { 47 | margin: 0 0 1.25rem; 48 | } 49 | 50 | .lightText { 51 | color: #666; 52 | } 53 | -------------------------------------------------------------------------------- /posts/ssg-ssr.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'When to Use Static Generation v.s. Server-side Rendering' 3 | date: '2020-01-02' 4 | --- 5 | 6 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request. 7 | 8 | You can use Static Generation for many types of pages, including: 9 | 10 | - Marketing pages 11 | - Blog posts 12 | - E-commerce product listings 13 | - Help and documentation 14 | 15 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation. 16 | 17 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request. 18 | 19 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data. 20 | -------------------------------------------------------------------------------- /pages/posts/[id].js: -------------------------------------------------------------------------------- 1 | import Layout from '../../components/layout'; 2 | import { getAllPostIds, getPostData } from '../../lib/posts'; 3 | import Head from 'next/head'; 4 | import Date from '../../components/date'; 5 | import utilStyles from '../../styles/utils.module.css'; 6 | 7 | export async function getStaticProps({ params }) { 8 | // Add the "await" keyword like this: 9 | const postData = await getPostData(params.id); 10 | 11 | return { 12 | props: { 13 | postData, 14 | }, 15 | }; 16 | } 17 | 18 | export async function getStaticPaths() { 19 | const paths = getAllPostIds(); 20 | return { 21 | paths, 22 | fallback: false, 23 | }; 24 | } 25 | 26 | export default function Post({ postData }) { 27 | return ( 28 | 29 | 30 | {postData.title} 31 | 32 |
33 |

{postData.title}

34 |
35 | 36 |
37 |
38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Layout, { siteTitle } from '../components/layout'; 3 | import utilStyles from '../styles/utils.module.css'; 4 | import { getSortedPostsData } from '../lib/posts'; 5 | import Link from 'next/link'; 6 | import Date from '../components/date'; 7 | 8 | export async function getStaticProps() { 9 | const allPostsData = getSortedPostsData(); 10 | return { 11 | props: { 12 | allPostsData, 13 | }, 14 | }; 15 | } 16 | 17 | export default function Home({ allPostsData }) { 18 | return ( 19 | 20 | 21 | {siteTitle} 22 | 23 |
24 |

Inventor, pseudojournalist, a contrarian's contrarian.

25 |

26 | (This is a sample website - you’ll be building a site like this on{' '} 27 | our Next.js tutorial.) 28 |

29 |
30 |
31 |

Blog

32 |
    33 | {allPostsData.map(({ id, date, title }) => ( 34 |
  • 35 | {title} 36 |
    37 | 38 | 39 | 40 |
  • 41 | ))} 42 |
43 |
44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | .title a { 11 | color: #0070f3; 12 | text-decoration: none; 13 | } 14 | 15 | .title a:hover, 16 | .title a:focus, 17 | .title a:active { 18 | text-decoration: underline; 19 | } 20 | 21 | .title { 22 | margin: 0 0 1rem; 23 | line-height: 1.15; 24 | font-size: 3.6rem; 25 | } 26 | 27 | .title { 28 | text-align: center; 29 | } 30 | 31 | .title, 32 | .description { 33 | text-align: center; 34 | } 35 | 36 | 37 | .description { 38 | line-height: 1.5; 39 | font-size: 1.5rem; 40 | } 41 | 42 | .grid { 43 | display: flex; 44 | align-items: center; 45 | justify-content: center; 46 | flex-wrap: wrap; 47 | 48 | max-width: 800px; 49 | margin-top: 3rem; 50 | } 51 | 52 | .card { 53 | margin: 1rem; 54 | flex-basis: 45%; 55 | padding: 1.5rem; 56 | text-align: left; 57 | color: inherit; 58 | text-decoration: none; 59 | border: 1px solid #eaeaea; 60 | border-radius: 10px; 61 | transition: color 0.15s ease, border-color 0.15s ease; 62 | } 63 | 64 | .card:hover, 65 | .card:focus, 66 | .card:active { 67 | color: #0070f3; 68 | border-color: #0070f3; 69 | } 70 | 71 | .card h3 { 72 | margin: 0 0 1rem 0; 73 | font-size: 1.5rem; 74 | } 75 | 76 | .card p { 77 | margin: 0; 78 | font-size: 1.25rem; 79 | line-height: 1.5; 80 | } 81 | 82 | .logo { 83 | height: 1em; 84 | } 85 | 86 | @media (max-width: 600px) { 87 | .grid { 88 | width: 100%; 89 | flex-direction: column; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/posts.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import matter from 'gray-matter'; 4 | import { remark } from 'remark'; 5 | import html from 'remark-html'; 6 | 7 | const postsDirectory = path.join(process.cwd(), 'posts'); 8 | 9 | export function getSortedPostsData() { 10 | // Get file names under /posts 11 | const fileNames = fs.readdirSync(postsDirectory); 12 | const allPostsData = fileNames.map((fileName) => { 13 | // Remove ".md" from file name to get id 14 | const id = fileName.replace(/\.md$/, ''); 15 | 16 | // Read markdown file as string 17 | const fullPath = path.join(postsDirectory, fileName); 18 | const fileContents = fs.readFileSync(fullPath, 'utf8'); 19 | 20 | // Use gray-matter to parse the post metadata section 21 | const matterResult = matter(fileContents); 22 | 23 | // Combine the data with the id 24 | return { 25 | id, 26 | ...matterResult.data, 27 | }; 28 | }); 29 | // Sort posts by date 30 | return allPostsData.sort((a, b) => { 31 | if (a.date < b.date) { 32 | return 1; 33 | } else { 34 | return -1; 35 | } 36 | }); 37 | } 38 | 39 | export function getAllPostIds() { 40 | const fileNames = fs.readdirSync(postsDirectory); 41 | 42 | // Returns an array that looks like this: 43 | // [ 44 | // { 45 | // params: { 46 | // id: 'ssg-ssr' 47 | // } 48 | // }, 49 | // { 50 | // params: { 51 | // id: 'pre-rendering' 52 | // } 53 | // } 54 | // ] 55 | return fileNames.map((fileName) => { 56 | return { 57 | params: { 58 | id: fileName.replace(/\.md$/, ''), 59 | }, 60 | }; 61 | }); 62 | } 63 | 64 | export async function getPostData(id) { 65 | const fullPath = path.join(postsDirectory, `${id}.md`); 66 | const fileContents = fs.readFileSync(fullPath, 'utf8'); 67 | 68 | // Use gray-matter to parse the post metadata section 69 | const matterResult = matter(fileContents); 70 | 71 | // Use remark to convert markdown into HTML string 72 | const processedContent = await remark() 73 | .use(html) 74 | .process(matterResult.content); 75 | const contentHtml = processedContent.toString(); 76 | 77 | // Combine the data with the id 78 | return { 79 | id, 80 | contentHtml, 81 | ...matterResult.data, 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Image from 'next/image'; 3 | import styles from './layout.module.css'; 4 | import utilStyles from '../styles/utils.module.css'; 5 | import Link from 'next/link'; 6 | 7 | const name = 'Andre Stackhouse'; 8 | export const siteTitle = 'Next.js Sample Website'; 9 | 10 | export default function Layout({ children, home }) { 11 | return ( 12 |
13 | 14 | 15 | 19 | 25 | 26 | 27 | 28 |
29 | {home ? ( 30 | <> 31 | 39 |

{name}

40 | 41 | ) : ( 42 | <> 43 | 44 | 52 | 53 |

54 | 55 | {name} 56 | 57 |

58 | 59 | )} 60 |
61 |
{children}
62 | {!home && ( 63 |
64 | ← Back to home 65 |
66 | )} 67 |
68 | ); 69 | } 70 | --------------------------------------------------------------------------------