├── 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 |
--------------------------------------------------------------------------------