From 23a3b467fad4c13ca2ed9e38225d390df30716ba Mon Sep 17 00:00:00 2001 From: Ryan Florence Date: Tue, 7 Dec 2021 09:13:59 -0700 Subject: [PATCH] Remix --- .gitignore | 8 +- README.md | 34 +- api/index.js | 5 + .../components/footer.js | 0 {components => app/components}/header.js | 25 +- .../item.client.js => app/components/item.js | 8 +- app/components/item.sc.js | 44 + {components => app/components}/logo.js | 0 {components => app/components}/meta.js | 14 +- {components => app/components}/nav.js | 28 +- .../page.client.js => app/components/page.js | 7 +- .../components}/server-info.server.js | 0 {components => app/components}/spinner.js | 0 .../components/stories.js | 12 +- app/components/story.js | 53 + app/entry.client.jsx | 4 + app/entry.server.jsx | 21 + {lib => app/lib}/fetch-data.js | 8 +- {lib => app/lib}/get-item.js | 0 {lib => app/lib}/get-story-ids.js | 0 {lib => app/lib}/time-ago.js | 0 {lib => app/lib}/use-data.js | 0 {pages => app/pages}/_app.js | 0 {pages => app/pages}/_document.js | 0 {pages => app/pages}/csr.js | 4 +- {pages => app/pages}/index.js | 0 app/pages/rsc.server.js | 51 + {pages => app/pages}/slow.server.js | 30 +- app/pages/ssr.js | 26 + app/root.jsx | 24 + components/item.js | 44 - components/story.client.js | 55 - jsconfig.json | 8 + next-env.d.ts | 6 - next.config.js | 7 - package-lock.json | 7940 +++++++++++++++++ package.json | 31 +- pages/rsc.server.js | 49 - pages/ssr.js | 33 - public/favicon.ico | Bin 0 -> 16958 bytes remix.config.js | 9 + tsconfig.json | 20 - vercel.json | 7 + yarn.lock | 1816 ---- 44 files changed, 8307 insertions(+), 2124 deletions(-) create mode 100644 api/index.js rename components/footer.client.js => app/components/footer.js (100%) rename {components => app/components}/header.js (77%) rename components/item.client.js => app/components/item.js (84%) create mode 100644 app/components/item.sc.js rename {components => app/components}/logo.js (100%) rename {components => app/components}/meta.js (86%) rename {components => app/components}/nav.js (59%) rename components/page.client.js => app/components/page.js (87%) rename {components => app/components}/server-info.server.js (100%) rename {components => app/components}/spinner.js (100%) rename components/stories.client.js => app/components/stories.js (86%) create mode 100644 app/components/story.js create mode 100644 app/entry.client.jsx create mode 100644 app/entry.server.jsx rename {lib => app/lib}/fetch-data.js (58%) rename {lib => app/lib}/get-item.js (100%) rename {lib => app/lib}/get-story-ids.js (100%) rename {lib => app/lib}/time-ago.js (100%) rename {lib => app/lib}/use-data.js (100%) rename {pages => app/pages}/_app.js (100%) rename {pages => app/pages}/_document.js (100%) rename {pages => app/pages}/csr.js (91%) rename {pages => app/pages}/index.js (100%) create mode 100644 app/pages/rsc.server.js rename {pages => app/pages}/slow.server.js (54%) create mode 100644 app/pages/ssr.js create mode 100644 app/root.jsx delete mode 100644 components/item.js delete mode 100644 components/story.client.js create mode 100644 jsconfig.json delete mode 100644 next-env.d.ts delete mode 100644 next.config.js create mode 100644 package-lock.json delete mode 100644 pages/rsc.server.js delete mode 100644 pages/ssr.js create mode 100644 public/favicon.ico create mode 100644 remix.config.js delete mode 100644 tsconfig.json create mode 100644 vercel.json delete mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index f866932..0f43d8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ node_modules -.next -*.log + +.cache .vercel +.output + +public/build +api/build diff --git a/README.md b/README.md index 31159a5..944936b 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,34 @@ -# Next.js 12 React Server Components Demo (Alpha) +# Welcome to Remix! -This is the demo of Hacker News built with Next.js and React Server Components. Read our announcement here: [Next.js 12](https://nextjs.org/blog/next-12). +- [Remix Docs](https://remix.run/docs) -**Try the demo: https://next-news-rsc.vercel.sh** +## Deployment -### Development +After having run the `create-remix` command and selected "Vercel" as a deployment target, you only need to [import your Git repository](https://vercel.com/new) into Vercel, and it will be deployed. -To get started, run the following commands: +If you'd like to avoid using a Git repository, you can also deploy the directory by running [Vercel CLI](https://vercel.com/cli): +```sh +npm i -g vercel +vercel ``` -yarn -yarn dev + +It is generally recommended to use a Git repository, because future commits will then automatically be deployed by Vercel, through its [Git Integration](https://vercel.com/docs/concepts/git). + +## Development + +To run your Remix app locally, make sure your project's local dependencies are installed: + +```sh +npm install ``` -And visit localhost:3000. +Afterwards, start the Remix development server like so: -### Note +```sh +npm run dev +``` -React Server Components are still [experimental](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html). To learn more about React Server Components, read our blog post: [Everything About React Server Components](https://vercel.com/blog/everything-about-react-server-components). +Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go! -React Server Components support is a built-in feature of Next.js 12. Full documentation is available here: [React 18 — Next.js](https://nextjs.org/docs/advanced-features/react-18). +If you're used to using the `vercel dev` command provided by [Vercel CLI](https://vercel.com/cli) instead, you can also use that, but it's not needed. diff --git a/api/index.js b/api/index.js new file mode 100644 index 0000000..14536af --- /dev/null +++ b/api/index.js @@ -0,0 +1,5 @@ +const { createRequestHandler } = require("@remix-run/vercel"); + +module.exports = createRequestHandler({ + build: require("./build") +}); diff --git a/components/footer.client.js b/app/components/footer.js similarity index 100% rename from components/footer.client.js rename to app/components/footer.js diff --git a/components/header.js b/app/components/header.js similarity index 77% rename from components/header.js rename to app/components/header.js index b2c09b7..a6e191b 100644 --- a/components/header.js +++ b/app/components/header.js @@ -1,6 +1,5 @@ -import Nav from './nav' -import Logo from './logo' -import Link from 'next/link' +import Nav from "./nav"; +import Logo from "./logo"; export default function Header() { return ( @@ -65,22 +64,22 @@ export default function Header() { />
- - - - - - Hacker Next - - + + + + + Hacker Next +
- login + + login +
- ) + ); } diff --git a/components/item.client.js b/app/components/item.js similarity index 84% rename from components/item.client.js rename to app/components/item.js index b36682c..33e0045 100644 --- a/components/item.client.js +++ b/app/components/item.js @@ -1,6 +1,6 @@ -import Story from '../components/story.client' -import Comment from '../components/comment' -import CommentForm from '../components/comment-form' +import Story from "./story"; +import Comment from "./comment"; +import CommentForm from "./comment-form"; export default function Item({ story, comments = null }) { return ( @@ -39,5 +39,5 @@ export default function Item({ story, comments = null }) { } `} - ) + ); } diff --git a/app/components/item.sc.js b/app/components/item.sc.js new file mode 100644 index 0000000..41ea7ba --- /dev/null +++ b/app/components/item.sc.js @@ -0,0 +1,44 @@ +import React, { Suspense } from "react"; +import { useRouter } from "next/router"; + +import Page from "./page"; +import Item from "./item"; + +import getItem from "../lib/get-item"; +import getComments from "../app/lib/get-comments"; + +let commentsData = {}; +let storyData = {}; +let fetchDataPromise = {}; + +function ItemPageWithData({ id }) { + if (!commentsData[id]) { + if (!fetchDataPromise[id]) { + fetchDataPromise[id] = getItem(id) + .then((story) => { + storyData[id] = story; + return getComments(story.comments); + }) + .then((c) => (commentsData[id] = c)); + } + throw fetchDataPromise[id]; + } + + return ( + + + + ); +} + +export default function ItemPage() { + const { id } = useRouter().query; + + if (!id) return null; + + return ( + + + + ); +} diff --git a/components/logo.js b/app/components/logo.js similarity index 100% rename from components/logo.js rename to app/components/logo.js diff --git a/components/meta.js b/app/components/meta.js similarity index 86% rename from components/meta.js rename to app/components/meta.js index dcef11d..6cf3e63 100644 --- a/components/meta.js +++ b/app/components/meta.js @@ -1,14 +1,8 @@ -import Head from "next/head"; -// import Router from 'next/router' - export default function Meta() { return ( -
- - - - {/* */} - + <> + +
-) +); diff --git a/app/components/story.js b/app/components/story.js new file mode 100644 index 0000000..3cf7ab2 --- /dev/null +++ b/app/components/story.js @@ -0,0 +1,53 @@ +import { useState } from "react"; + +import timeAgo from "../lib/time-ago"; + +export default function Story({ + id, + title, + date, + url, + user, + score, + commentsCount, +}) { + const { host } = url ? new URL(url) : { host: "#" }; + const [voted, setVoted] = useState(false); + + return ( +
+
+ setVoted(!voted)} + > + ▲ + + {title} + {url && ( + + {host.replace(/^www\./, "")} + + )} +
+
+ {score} {plural(score, "point")} by{" "} + {user}{" "} + + {timeAgo(new Date(date)) /* note: we re-hydrate due to ssr */} ago + {" "} + |{" "} + + {commentsCount} {plural(commentsCount, "comment")} + +
+
+ ); +} + +const plural = (n, s) => s + (n === 0 || n > 1 ? "s" : ""); diff --git a/app/entry.client.jsx b/app/entry.client.jsx new file mode 100644 index 0000000..a19979b --- /dev/null +++ b/app/entry.client.jsx @@ -0,0 +1,4 @@ +import { hydrate } from "react-dom"; +import { RemixBrowser } from "remix"; + +hydrate(, document); diff --git a/app/entry.server.jsx b/app/entry.server.jsx new file mode 100644 index 0000000..7f1da6c --- /dev/null +++ b/app/entry.server.jsx @@ -0,0 +1,21 @@ +import { renderToString } from "react-dom/server"; +import { RemixServer } from "remix"; + +export default function handleRequest( + request, + responseStatusCode, + responseHeaders, + remixContext +) { + let markup = renderToString( + + ); + + responseHeaders.set("Content-Type", "text/html"); + responseHeaders.set("Cache-Control", "s-maxage=5, stale-while-revalidate=5"); + + return new Response("" + markup, { + status: responseStatusCode, + headers: responseHeaders, + }); +} diff --git a/lib/fetch-data.js b/app/lib/fetch-data.js similarity index 58% rename from lib/fetch-data.js rename to app/lib/fetch-data.js index dc0ddc4..3920f11 100644 --- a/lib/fetch-data.js +++ b/app/lib/fetch-data.js @@ -1,10 +1,10 @@ export default async function fetchData(type, delay = 0) { const [res] = await Promise.all([ fetch(`https://hacker-news.firebaseio.com/v0/${type}.json`), - new Promise(res => setTimeout(res, (Math.random()) * delay)) - ]) + new Promise((res) => setTimeout(res, Math.random() * delay)), + ]); if (res.status !== 200) { - throw new Error(`Status ${res.status}`) + throw new Error(`Status ${res.status}`); } - return res.json() + return res.json(); } diff --git a/lib/get-item.js b/app/lib/get-item.js similarity index 100% rename from lib/get-item.js rename to app/lib/get-item.js diff --git a/lib/get-story-ids.js b/app/lib/get-story-ids.js similarity index 100% rename from lib/get-story-ids.js rename to app/lib/get-story-ids.js diff --git a/lib/time-ago.js b/app/lib/time-ago.js similarity index 100% rename from lib/time-ago.js rename to app/lib/time-ago.js diff --git a/lib/use-data.js b/app/lib/use-data.js similarity index 100% rename from lib/use-data.js rename to app/lib/use-data.js diff --git a/pages/_app.js b/app/pages/_app.js similarity index 100% rename from pages/_app.js rename to app/pages/_app.js diff --git a/pages/_document.js b/app/pages/_document.js similarity index 100% rename from pages/_document.js rename to app/pages/_document.js diff --git a/pages/csr.js b/app/pages/csr.js similarity index 91% rename from pages/csr.js rename to app/pages/csr.js index 08a84f5..7240896 100644 --- a/pages/csr.js +++ b/app/pages/csr.js @@ -4,8 +4,8 @@ import { Suspense } from "react"; import Spinner from "../components/spinner"; // Client Components -import Page from "../components/page.client"; -import Story from "../components/story.client"; +import Page from "../components/page"; +import Story from "../components/story"; // Utils import fetchData from "../lib/fetch-data"; diff --git a/pages/index.js b/app/pages/index.js similarity index 100% rename from pages/index.js rename to app/pages/index.js diff --git a/app/pages/rsc.server.js b/app/pages/rsc.server.js new file mode 100644 index 0000000..1488f78 --- /dev/null +++ b/app/pages/rsc.server.js @@ -0,0 +1,51 @@ +import { Suspense } from "react"; + +// Shared Components +import Spinner from "../components/spinner"; + +// Server Components +import SystemInfo from "../components/server-info.server"; + +// Client Components +import Page from "../components/page"; +import Story from "../components/story"; +import Footer from "../components/footer"; + +// Utils +import fetchData from "../lib/fetch-data"; +import { transform } from "../lib/get-item"; +import useData from "../lib/use-data"; + +function StoryWithData({ id }) { + const data = useData(`s-${id}`, () => + fetchData(`item/${id}`).then(transform) + ); + return ; +} + +function NewsWithData() { + const storyIds = useData("top", () => fetchData("topstories")); + return ( + <> + {storyIds.slice(0, 30).map((id) => { + return ( + } key={id}> + + + ); + })} + + ); +} + +export default function News() { + return ( + + }> + + +