Skip to content

feat: add nextjs-blog example #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed blog/prisma/dev.db
Binary file not shown.
7 changes: 0 additions & 7 deletions blog/tsconfig.json

This file was deleted.

4 changes: 4 additions & 0 deletions integration-nextjs/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["next/babel"],
"plugins": ["@babel/plugin-proposal-export-default-from"]
}
32 changes: 32 additions & 0 deletions integration-nextjs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

.now
7 changes: 7 additions & 0 deletions integration-nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This combines what one builds in https://nextjs.org/learn with https://nexusjs.org.

The Nexus additions just show how to integrate Nexus into your app. It does not serve any practical purpose. You can use the API to query posts.

This example is incomplete. It depends upon the resolution of https://github.com/graphql-nexus/nexus/issues/648. Its current limitations are:

1. Dev modes are not integrated. You need to run `npx next dev` and `npm run nexus:reflection` in their own separate terminals. The latter is simply used to run reflection. The Nexus server is actually run by Nextjs.
6 changes: 6 additions & 0 deletions integration-nextjs/components/date.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { format, parseISO } from "date-fns";

export default function Date({ dateString }) {
const date = parseISO(dateString);
return <time dateTime={dateString}>{format(date, "LLLL d, yyyy")}</time>;
}
25 changes: 25 additions & 0 deletions integration-nextjs/components/layout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.container {
max-width: 36rem;
padding: 0 1rem;
margin: 3rem auto 6rem;
}

.header {
display: flex;
flex-direction: column;
align-items: center;
}

.headerImage {
width: 6rem;
height: 6rem;
}

.headerHomeImage {
width: 8rem;
height: 8rem;
}

.backToHome {
margin: 3rem 0 0;
}
68 changes: 68 additions & 0 deletions integration-nextjs/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Head from "next/head";
import Link from "next/link";
import utilStyles from "../styles/utils.module.css";
import styles from "./layout.module.css";

const name = "Newton";
export const siteTitle = "Next.js × Nexus";

export default function Layout(input: { children: any; home?: boolean }) {
const children = input.children;
const home = input.home ?? false;
return (
<div className={styles.container}>
<Head>
<link rel="icon" href="/favicon.ico" />
<meta
name="description"
content="Learn how to build a personal website using Next.js"
/>
<meta
property="og:image"
content={`https://og-image.now.sh/${encodeURI(
siteTitle
)}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.zeit.co%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
/>
<meta name="og:title" content={siteTitle} />
<meta name="twitter:card" content="summary_large_image" />
</Head>
<header className={styles.header}>
{home ? (
<>
<img
src="https://www.biography.com/.image/ar_1:1%2Cc_fill%2Ccs_srgb%2Cg_face%2Cq_auto:good%2Cw_300/MTY2MzU3Njc4ODI0NjI5OTM0/isaac_newton_1689_painting_sir_godfrey_kneller_public_domain_via_wikimedia_commons.jpg"
className={`${styles.headerHomeImage} ${utilStyles.borderCircle}`}
alt={name}
/>
<h1 className={utilStyles.heading2Xl}>{name}</h1>
</>
) : (
<>
<Link href="/">
<a>
<img
src="https://www.biography.com/.image/ar_1:1%2Cc_fill%2Ccs_srgb%2Cg_face%2Cq_auto:good%2Cw_300/MTY2MzU3Njc4ODI0NjI5OTM0/isaac_newton_1689_painting_sir_godfrey_kneller_public_domain_via_wikimedia_commons.jpg"
className={`${styles.headerImage} ${utilStyles.borderCircle}`}
alt={name}
/>
</a>
</Link>
<h2 className={utilStyles.headingLg}>
<Link href="/">
<a className={utilStyles.colorInherit}>{name}</a>
</Link>
</h2>
</>
)}
</header>
<main>{children}</main>
{!home && (
<div className={styles.backToHome}>
<Link href="/">
<a>← Back to home</a>
</Link>
</div>
)}
</div>
);
}
11 changes: 11 additions & 0 deletions integration-nextjs/db/data/posts/pre-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: "Two Forms of Pre-rendering"
date: "2020-01-01"
---

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.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

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.
19 changes: 19 additions & 0 deletions integration-nextjs/db/data/posts/ssg-ssr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: "When to Use Static Generation v.s. Server-side Rendering"
date: "2020-01-02"
---

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.

You can use Static Generation for many types of pages, including:

- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation

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.

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.

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.
54 changes: 54 additions & 0 deletions integration-nextjs/db/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import fs from "fs";
import matter from "gray-matter";
import path from "path";
import remark from "remark";
import html from "remark-html";

export type Post = {
date: string;
content: string;
id: string;
title: string;
};

const postsDir = path.join(process.cwd(), "db", "data", "posts");

export async function getSortedPosts(): Promise<Post[]> {
const fileNames = fs.readdirSync(postsDir);

const allPostsData = await Promise.all(
fileNames.map(async (fileName) => {
// Remove ".md" from file name to get id
const id = fileName.replace(/\.md$/, "");

// Read markdown file as string
const fullPath = path.join(postsDir, fileName);
const fileContents = fs.readFileSync(fullPath, "utf8");

// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents);

const content = await remark()
.use(html)
.process(matterResult.content)
.then((html) => html.toString());

// Combine the data with the id
return {
id: id,
title: matterResult.data.title,
date: matterResult.data.date,
content: content,
} as Post;
})
);

// Sort posts by date
return allPostsData.sort((a: any, b: any) => {
if (a.date < b.date) {
return 1;
} else {
return -1;
}
});
}
4 changes: 4 additions & 0 deletions integration-nextjs/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "remark-html" {
const html: any;
export default html;
}
66 changes: 66 additions & 0 deletions integration-nextjs/graphql/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import bodyParser from "body-parser";
import cors from "cors";
import { log, schema, server } from "nexus";
import { Post } from "../db/post";

function getSortedPosts(): Promise<Post[]> {
return Promise.resolve([
{
content:
"Me: Dear World, teach me how APIs access private static files in NextJS.\n\nWorld: Read this young one: https://github.com/zeit/next.js/issues/8251.",
date: "2020-01-13",
id: "1",
title: "Hello world",
},
]);
}

server.express.use(cors());
server.express.use(bodyParser.json());
server.express.use((req, _res, next) => {
if (req.body.operationName === "IntrospectionQuery") {
log.trace("request", {
path: req.path,
method: req.method,
body: req.body,
query: req.query,
});
} else {
log.debug("incoming graphql operation", req.body);
}
next();
});

schema.objectType({
name: "Post",
rootTyping: "Post",
definition(t) {
t.id("id");
t.string("title");
t.string("date");
t.string("content");
},
});

schema.queryType({
definition(t) {
t.field("post", {
type: "Post",
args: {
id: schema.idArg({ required: true }),
},
resolve(_, args) {
return getSortedPosts().then((posts) =>
posts.find((p) => p.id === args.id)
);
},
});

t.list.field("posts", {
type: "Post",
resolve() {
return getSortedPosts();
},
});
},
});
70 changes: 70 additions & 0 deletions integration-nextjs/lib/posts.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { request } from "graphql-request";

type Post = {
date: string;
content: string;
id: string;
title: string;
};

export async function getSortedPosts(): Promise<Post[]> {
const query = `{
posts {
id
title
date
content
}
}`;

const data: { posts: Post[] } = await request(
"http://localhost:4000/graphql",
query
);

return data.posts.sort((a: any, b: any) => {
if (a.date < b.date) {
return 1;
} else {
return -1;
}
});
}

export async function getAllPostIds() {
const data: { posts: { id: string }[] } = await request(
"http://localhost:4000/graphql",
`{
posts {
id
}
}
`
);

return data.posts.map((post) => {
return {
params: {
id: post.id,
},
};
});
}

export async function getPostData(id): Promise<Post> {
const operation = `{
post(id: "${id}") {
id
title
date
content
}
}`;

const data: { post: Post } = await request(
"http://localhost:4000/graphql",
operation
);

return data.post;
}
2 changes: 2 additions & 0 deletions integration-nextjs/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
Loading