├── .gitignore ├── FUNDING.yml ├── LICENSE ├── README.md ├── components ├── Author.tsx ├── BlogPost.tsx ├── Code.tsx ├── Footer.tsx ├── Header.tsx ├── Markdown.tsx ├── Meta.tsx ├── PostCard.tsx ├── PostMeta.tsx └── Tag.tsx ├── globals.ts ├── index.html ├── loader.ts ├── md ├── blog │ ├── dan-abramov.md │ ├── devii.md │ └── the-ultimate-tech-stack.md ├── features.md └── introduction.md ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── blog │ └── [blog].tsx └── index.tsx ├── public ├── 404.html ├── favicon.ico ├── img │ ├── brook.jpg │ ├── brook_thumb.jpg │ ├── colin_square_small.jpg │ ├── danabramov.png │ ├── danabramov_thumb.png │ ├── pancakes.jpeg │ ├── pancakes_thumb.jpeg │ ├── profile.jpg │ └── rss-white.svg ├── index.html └── rss.xml ├── rssUtil.ts ├── sitemap.ts ├── styles └── base.css ├── tsconfig.json └── 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 | /build/ 15 | /lib/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | .env* 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | firebase.json 30 | .firebaserc 31 | .firebase/* 32 | .nvmrc 33 | .vscode -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: colinhacks -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Colin McDonnell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
3 | A developer blog starter for 2020.
Next.js
React
TypeScript
Markdown
syntax highlighting
SEO
RSS generation
4 |
6 | if you're happy and you know it, star this repo 7 |
8 |
9 |
10 |
11 |
12 |
173 | ```ts 174 | // pretty neat huh? 175 | const test = (arg: string) => { 176 | return arg.length > 5; 177 | }; 178 | ```179 | 180 | turns into 181 | 182 | ```ts 183 | // pretty neat huh? 184 | const test = (arg: string) => { 185 | return arg.length > 5; 186 | }; 187 | ``` 188 | 189 | View `/components/Code.tsx` to see how this works or customize this behavior. 190 | 191 | ## Markdown loading 192 | 193 | _You don't need to understand all of this to use Devii. Consider this an "advanced guide" you can use if you want to customize the structure of the site._ 194 | 195 | Markdown posts are loaded during Next.js static build step. Check out the [Data Fetching](https://nextjs.org/docs/basic-features/data-fetching) documentation to learn more about this. 196 | 197 | Here's the short version: if export a function called `getStaticProps` from one of your page components, Next.js will execute that function, take the result, and pass the `props` property (which should be another object) into your page as props. 198 | 199 | You can dynamically load and parse a Markdown file using `loadMarkdownFile`, a utility function implemented in `loader.ts`. It is an async function that returns a `PostData` TypeScript object containing all the metadata keys listed above: 200 | 201 | For an example of this, check out the `getStaticProps` implementation from the homepage. The function calls `loadBlogPosts` - a utilty function that loads _every_ blog posts in the `/md/blog/` directory, parses them, and returns `PostData[]`. 202 | 203 | ```ts 204 | export const getStaticProps = async () => { 205 | const posts = await loadBlogPosts(); 206 | return { props: { posts } }; 207 | }; 208 | ``` 209 | 210 | There are a few utility functions in `loader.ts` that Devii uses. All functions are _async_! All functions accept a _relative_ path which is expected to be \_relative to the `md/` directory. For instance `loadPost('blog/test.md'`) would load `/md/blog/test.md`. 211 | 212 | - `loadPost` loads/parses a Markdown file and returns a `PostData` 213 | - `loadBlogPosts`: loads/parses all the files in `/md/blog/`. Returns `PostData[]`. Used in `index.tsx` to load/render a list of all published blog posts 214 | - `loadMarkdownFile`: loads a Markdown file but doesn't parse it. Returns the string content. Useful if you want to implement some parts of a page in Markdown and other parts in React 215 | - `loadMarkdownFiles`: accepts a [glob](https://docs.python.org/3/library/glob.html) pattern and loads all the files inside `/md/` whose names match the pattern. Used internally by `loadBlogPosts` 216 | 217 | ## Static generation 218 | 219 | You can generate a fully static version of your site using `yarn build && yarn export`. This step is entirely powered by Next.js. The static site is exported to the `out` directory. 220 | 221 | After it's generated, use your static file hosting service of choice (Vercel, Netlify, Firebase Hosting, Amazon S3) to deploy your site. 222 | 223 | ## Global configs 224 | 225 | There is a `globals.ts` file in the project root containing some settings/configuration metadata about your site: 226 | 227 | - `yourName`: Your name, used for the copyright tags in the footer and the RSS feed, e.g. Alyssa P. Hacker 228 | - `siteName`: The title of your blog, e.g. `Alyssa's Cool Blog`; 229 | - `siteDescription`: A short description, used in the `meta` description tag, e.g. 'I write about code \'n stuff'; 230 | - `siteCreationDate`: Used in the generated RSS feed. Use this format: 'March 3, 2020 04:00:00 GMT'; 231 | - `twitterHandle`: The twitter handle for you or your blog/company, used in the Twitter meta tags. Include the @ symbol, e.g. '@alyssaphacker'; 232 | - `email`: Your email, used as the "webMaster" and "managingEditor" field of the generated RSS feed, e.g. `alyssa@example.com`; 233 | - `url`: The base URL of your website, used to "compute" default canonical links from relative paths, e.g. 'https://alyssaphacker.com'; 234 | - `accentColor`: The header and footer background color, e.g. `#4fc2b4`; 235 | 236 | ## RSS feed generation 237 | 238 | An RSS feed is auto-generated from your blog post feed. This feed is generated using the `rss` module (for converting JSON to RSS format) and `showdown` for converting the markdown files to RSS-compatible HTML. 239 | 240 | For RSS generation to work, all your posts must contain a `datePublished` timestamp in their frontmatter metadata. To examine or customize the RSS generation, check out the `rssUtil.ts` file in the root directory. 241 | 242 | ## SEO 243 | 244 | Every blog post page automatically populated meta tags based on the post metadata. This includes a `title` tag, `meta` tags, `og:` tags, Twitter metadata, and a `link` tag containing the canonical URL. 245 | 246 | The default value of the canonical URL is computed by concatenating the value of your `url` config (see Global Configs above) and the relative path of your post. Verify that the canonical URL is exactly equivalent to the URL in the browser when visiting your live site, otherwise your site's SEO may suffer. 247 | 248 | ## Insanely customizable 249 | 250 | There's nothing "under the hood" here. You can view and modify all the files that provide the functionality listed above. Devii just provides a project scaffold, some Markdown-loading loading utilities (in `loader.ts`), and some sensible styling defaults (especially in `Markdown.tsx`). 251 | 252 | To get started customizing, check out the source code of `index.tsx` (the home page), `BlogPost.tsx` (the blog post template), and `Markdown.tsx` (the Markdown renderer). 253 | 254 | Head to the GitHub repo to get started: [https://github.com/colinhacks/devii](https://github.com/colinhacks/devii). If you like this project, leave a ⭐️star⭐️ to help more people find Devii 😎 255 | 256 | # CLI 257 | 258 | ### `yarn dev` 259 | 260 | Starts the development server. Equivalent to `next dev`. 261 | 262 | ### `yarn build` 263 | 264 | Creates an optimized build of your site. Equivalent to `next build`. 265 | 266 | ### `yarn export` 267 | 268 | Exports your site to static files. All files are written to `/out`. Use your static file hosting service of choice (Firebase Hosting, Amazon S3, Vercel) to deploy your site. Equivalent to `next export`. 269 | -------------------------------------------------------------------------------- /components/Author.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { format } from 'fecha'; 3 | import { PostData } from '../loader'; 4 | 5 | export const FollowButton = () => { 6 | return ( 7 | 8 |
30 | {props.post.author && {props.post.author}} 31 | 32 | {props.post.authorTwitter && ( 33 | 34 | {' '} 35 | {`@${props.post.authorTwitter}`}{' '} 38 | 39 | )} 40 |
41 |42 | {props.post.datePublished 43 | ? format(new Date(props.post.datePublished), 'MMMM Do, YYYY') 44 | : ''} 45 |
46 |{`© ${globals.yourName} ${new Date().getFullYear()}`}
7 | 8 |{post.subtitle}
} 20 |21 | {props.post.datePublished 22 | ? format(new Date(props.post.datePublished), 'MMMM Do, YYYY') 23 | : ''} 24 |
25 |` tags in a div — is just annoying enough to bug me. The answer: Markdown of course!
40 |
41 | Static site generators (SSGs) like Hugo and Jekyll provide an undeniably wonderful authoring experience. All you have to do is `touch` a new .md file in the proper directory and get to writing. Unfortunately all Markdown-based SSGs I know of are too restrictive. Mixing React and Markdown on the same page is either impossible or tricky. If it's possible, it likely requires some plugin/module/extension, config file, blob of boilerplate, or egregious hack. Sorry Hugo, I'm not going to re-write my React code using `React.createElement` like it's 2015.
42 |
43 | Well, that doesn't work for me. I want my website to be React-first, with a sprinkling of Markdown when it makes my life easier.
44 |
45 | ### Static generation
46 |
47 | As much as I love the Jamstack, it doesn't cut it from an SEO perspective. Many blogs powered by a "headless CMS" require two round trips before rendering the blog content (one to fetch the static JS bundle and another to fetch the blog content from a CMS). This degrades page load speeds and user experience, which accordingly degrades your rankings on Google.
48 |
49 | Instead I want every page of my site to be pre-rendered to a set of fully static assets, so I can deploy them to a CDN and get fast page loads everywhere. You could get the same benefits with server-side rendering, but that requires an actual server and worldwide load balancing to achieve comparable page load speeds. I love overengineering things as much as the next guy, even I have a line. 😅
50 |
51 | ## My solution
52 |
53 | I describe my final architecture design below, along with my rationale for each choice. I distilled this setup into a website starter/boilerplate available here: https://github.com/colinhacks/devii. Below, I allude to certain files/functions I implemented; to see the source code of these, just clone the repo `git clone git@github.com:colinhacks/devii.git`
54 |
55 | ### Next.js
56 |
57 | I chose to build my site with Next.js. This won't be a surprising decision to anyone who's played with statically-rendered or server-side rendered React in recent years. Next.js is quickly eating everyone else's lunch in this market, especially Gatsby's (sorry Gatsby fans).
58 |
59 | Next.js is by far the most elegant way (for now) to do any static generation or server-side rendering with React. They just released their next-generation (pun intended) static site generator in the [9.3 release](https://nextjs.org/blog/next-9-3) back in March. So in the spirit of using technologies [in the spring of their life](https://www.youtube.com/watch?v=eBAX8MbRYFA), Next.js is a no-brainer.
60 |
61 | Here's a quick breakdown of the project structure. No need to understand every piece of it; but it may be useful to refer to throughout the rest of this post.
62 |
63 | ```
64 | .
65 | ├── README.md
66 | ├── public // all static files (images, etc) go here
67 | ├── pages // every .tsx component in this dir becomes a page of the final site
68 | | ├── index.tsx // the home page (which has access to the list of all blog posts)
69 | | ├── blog
70 | | ├── [blog].md // a template component that renders the blog posts under `/md/blog`
71 | ├── md
72 | | ├── blog
73 | | ├── devii.md // this page!
74 | ├── whatever.md // every MD file in this directory becomes a blog post
75 | ├── components
76 | | ├── Code.tsx
77 | | ├── Markdown.tsx
78 | | ├──
35 | This section demonstrates the power of dynamic imports. Every Markdown
36 | file under
53 | Seems like it might be useful!
54 |
56 | — Dan Abramov, taken{' '}
57 |
61 | {' '}
62 | utterly out of context
63 |
64 |
71 | Below is the README.md for devii. It was imported and rendered using
72 | Next.js dynamic imports. The rest of this page (including this
73 | paragraph) are rendered with React. You can also read the README on
74 | GitHub at{' '}
75 |
76 | https://github.com/colinhacks/devii
77 |
78 | .
79 | The specified file was not found on this website. Please check the URL for mistakes and try again. This page was generated by the Firebase Command-Line Interface. To modify it, edit the You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary! Firebase SDK Loading…
135 | ```ts
136 | // pretty neat huh?
137 | const test = (arg: string) => {
138 | return arg.length > 5;
139 | };
140 | ```
141 |
142 | turns into this:
143 |
144 | ```ts
145 | // pretty neat huh?
146 | const test = (arg: string) => {
147 | return arg.length > 5;
148 | };
149 | ```
150 |
151 | ### RSS feed generation
152 |
153 | An RSS feed is auto-generated from your blog post feed. This feed is generated using the `rss` module (for converting JSON to RSS format) and `showdown` for converting the markdown files to RSS-compatible HTML. The feed is generated during the build step and written as a static file to `/rss.xml` in your static assets folder. It's dead simple. That's the joy of being able to easily write custom build scripts on top of Next.js's `getStaticProps` hooks!
154 |
155 | ### SEO
156 |
157 | Every blog post page automatically populated meta tags based on the post metadata. This includes a `title` tag, `meta` tags, `og:` tags, Twitter metadata, and a `link` tag containing the canonical URL. You can modify/augment this in the `PostMeta.ts` component.
158 |
159 | ### Static generation
160 |
161 | You can generate a fully static version of your site using `yarn build && yarn export`. This step is entirely powered by Next.js. The static site is exported to the `out` directory.
162 |
163 | After its generated, use your static file hosting service of choice (Firebase Hosting, Vercel, Netlify) to deploy your site.
164 |
165 | ### Insanely customizable
166 |
167 | There's nothing "under the hood" here. You can view and modify all the files that provide the functionality described above. Devii just provides a project scaffold, some Markdown-loading loading utilities (in `loader.ts`), and some sensible styling defaults (especially in `Markdown.tsx`).
168 |
169 | To start customizing, modify `index.tsx` (the home page), `Essay.tsx` (the blog post template), and `Markdown.tsx` (the Markdown renderer).
170 |
171 | ## Get started
172 |
173 | Head to the GitHub repo to get started: [https://github.com/colinhacks/devii](https://github.com/colinhacks/devii). If you like this project, leave a ⭐️star⭐️ to help more people find Devii! 😎
174 |
175 | To jump straight into the code, clone the repo and start the development server like so:
176 |
177 | ```bash
178 | git clone git@github.com:colinhacks/devii.git mysite
179 | cd mysite
180 | yarn
181 | yarn dev
182 | ```
183 |
--------------------------------------------------------------------------------
/md/features.md:
--------------------------------------------------------------------------------
1 | It may not look like much, but Devii does a lot out of the box.
2 |
3 | **Markdown loading and rendering**: Using Next.js dynamic imports, you can load Markdown files and pass them into your Next.js pages as props. Easy peasy.
4 |
5 | **TypeScript + React**: Markdown is great for text-heavy, non-interactive content. For everything else, you'll want something a little more expressive. Devii makes it easy to mix Markdown and React on the same page. Just load your Markdown files with dynamic imports, pass it into your component as a prop, and render it with the `Markdown.tsx` component.
6 |
7 | **Built-in support for blogs**: Devii provides a utility for parsing Markdown blog posts with frontmatter metadata into a structured TypeScript object. Supported tags include: `title`, `subtitle`, `datePublished`, `tags`, `description`, `canonicalUrl`, `author`, `authorPhoto`, `authorTwitter`, `bannerPhoto`, and `thumbnailPhoto`
8 |
9 | **Medium-inspired styles**: The Markdown components (`Markdown.tsx`) contains default styles inspired by Medium.
10 |
11 | **Google Analytics**: Just add your Google Analytics ID (e.g. 'UA-999999999-1') to `globals.ts` and the appropriate snippet will be automatically added to every page.
12 |
13 | **RSS feed generation**: An RSS feed is auto-generated from your blog post feed.
14 |
15 | **SEO best practices**: Every blog post page automatically populated meta tags based on the post metadata. This includes a `title` tag, `meta` tags, `og:` tags, Twitter metadata, and a `link` tag containing the canonical URL.
16 |
17 | **GitHub-style code blocks**: with syntax highlighting powered by [react-syntax-highlighter](https://github.com/conorhastings/react-syntax-highlighter). Works out-of-the-box for all programming languages. Just use Markdown's triple backtick syntax with a "language identifier", [just like GitHub](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks).
18 |
19 | **Static generation**: you can generate a fully static version of your site using `yarn build && yarn export`. Powered by Next.js.
20 |
21 | **Zero magic**: You can view and modify every aspect of the site. If you're looking for a starting point, start modifying `index.tsx` (the home page), `BlogPost.tsx` (the blog post template), and `Markdown.tsx` (the Markdown component). And of course you can add entirely new pages/components as well!
22 |
--------------------------------------------------------------------------------
/md/introduction.md:
--------------------------------------------------------------------------------
1 | Devii is a starter kit for building your personal developer website. Powered by the best technologies 2020 has to offer.
2 |
3 | It's not a a framework or a library, it's a just a simple project that contains some useful utilities and patterns that'll help you hit the ground running. In fact, the [GitHub repo for Devii](https://github.com/colinhacks/devii) contains the code for the site you're currently reading!
4 |
5 | Devii doesn't try to be a fully functional blog out of the box. After cloning/forking the repo, you'll need to delete the contents of `index.tsx` (the page you're reading now!) and implement your own homepage. Devii makes it easier — for instance, you can access a list of all your blog posts in `props.posts` — but you still have to build the site you're imagining in your mind's eye.
6 |
7 | And that's the point! After you clone/fork it, look through this code to learn how Devii works. Then rip out what you don't like, customize everything else, and build your own tools and components on top of the foundation Devii provides!
8 |
9 | Devii was designed to place _zero restrictions_ on what your site can be or become. You can use any React component or styling library, pull in data from third-party APIs, even implement user accounts. Your personal website is the online manifestation of you. Don't compromise.
10 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | /// Introduction to Devii
22 | Features
27 | My blog posts
34 | /md/blog
is automatically parsed into a
37 | structured TypeScript object and available in the{' '}
38 | props.posts
array. These blog post "cards" are
39 | implemented in the
40 | /components/PostCard.tsx
component.
41 | Testimonials
51 |
52 |
66 | README.md
70 | Get started
90 |
91 |
92 |
93 | 404
27 | Page Not Found
28 | Why am I seeing this?
30 | 404.html
file in your project's configured public
directory.Welcome
36 | Firebase Hosting Setup Complete
37 |
3 |6 |Seems like it might be useful! 4 | — Dan Abramov, taken entirely out of context
5 |
I don't want to brag, but Devii is kind of a big deal.
]]>https://alyssaphacker.com/blog/dan-abramovOriginally published at https://colinhacks.com/essays/devii. Check out the HN roast discussion here! 🤗
I recently set out to build my personal website — the one you're reading now, as it happens!
10 |Surprisingly, it was much harder than expected to put together a "tech stack" that met my criteria. My criteria are pretty straightforward; I would expect most React devs to have a similar list. Yet it was surprisingly hard to put all these pieces together.
11 |Given the lack of a decent out-of-the-box solution, I worry that many developers are settling for static-site generators that place limits on the interactivity and flexibility of your website. We can do better.
12 |13 |15 |Clone the repo here to get started with this setup: https://github.com/colinhacks/devii
14 |
Let's quickly run through my list of design goals:
16 |I want to build the site with React and TypeScript. I love them both wholeheartedly, I use them for my day job, and they're gonna be around for a long time. Plus writing untyped JS makes me feel dirty.
18 |I don't want limitations on what my personal website can be/become. Sure, at present my site consists of two simple, static blog posts. But down the road, I may want to build a page that contains an interactive visualization, a filterable table, or a demo of a React component I'm open-sourcing. Even something simple (like the email newsletter signup form at the bottom of this page) was much more pleasant to implement in React; how did we use to build forms again?
19 |Plus: I want access to the npm ecosystem and all my favorite UI, animation, and styling libraries. I sincerely hope I never write another line of raw CSS ever again; CSS-in-JS 4 lyfe baby. If you want to start a Twitter feud with me about this, by all means at me.
20 |If it's obnoxious to write new blog posts, I won't do it. That's a regrettable law of the universe. Even writing blog posts with plain HTML — just a bunch of <p>
tags in a div — is just annoying enough to bug me. The answer: Markdown of course!
Static site generators (SSGs) like Hugo and Jekyll provide an undeniably wonderful authoring experience. All you have to do is touch
a new .md file in the proper directory and get to writing. Unfortunately all Markdown-based SSGs I know of are too restrictive. Mixing React and Markdown on the same page is either impossible or tricky. If it's possible, it likely requires some plugin/module/extension, config file, blob of boilerplate, or egregious hack. Sorry Hugo, I'm not going to re-write my React code using React.createElement
like it's 2015.
Well, that doesn't work for me. I want my website to be React-first, with a sprinkling of Markdown when it makes my life easier.
24 |As much as I love the Jamstack, it doesn't cut it from an SEO perspective. Many blogs powered by a "headless CMS" require two round trips before rendering the blog content (one to fetch the static JS bundle and another to fetch the blog content from a CMS). This degrades page load speeds and user experience, which accordingly degrades your rankings on Google.
26 |Instead I want every page of my site to be pre-rendered to a set of fully static assets, so I can deploy them to a CDN and get fast page loads everywhere. You could get the same benefits with server-side rendering, but that requires an actual server and worldwide load balancing to achieve comparable page load speeds. I love overengineering things as much as the next guy, even I have a line. 😅
27 |I describe my final architecture design below, along with my rationale for each choice. I distilled this setup into a website starter/boilerplate available here: https://github.com/colinhacks/devii. Below, I allude to certain files/functions I implemented; to see the source code of these, just clone the repo git clone git@github.com:colinhacks/devii.git
I chose to build my site with Next.js. This won't be a surprising decision to anyone who's played with statically-rendered or server-side rendered React in recent years. Next.js is quickly eating everyone else's lunch in this market, especially Gatsby's (sorry Gatsby fans).
31 |Next.js is by far the most elegant way (for now) to do any static generation or server-side rendering with React. They just released their next-generation (pun intended) static site generator in the 9.3 release back in March. So in the spirit of using technologies in the spring of their life, Next.js is a no-brainer.
32 |Here's a quick breakdown of the project structure. No need to understand every piece of it; but it may be useful to refer to throughout the rest of this post.
33 |.
34 | ├── README.md
35 | ├── public // all static files (images, etc) go here
36 | ├── pages // every .tsx component in this dir becomes a page of the final site
37 | | ├── index.tsx // the home page (which has access to the list of all blog posts)
38 | | ├── blog
39 | | ├── [blog].md // a template component that renders the blog posts under `/md/blog`
40 | ├── md
41 | | ├── blog
42 | | ├── devii.md // this page!
43 | ├── whatever.md // every MD file in this directory becomes a blog post
44 | ├── components
45 | | ├── Code.tsx
46 | | ├── Markdown.tsx
47 | | ├── <various others>
48 | ├── loader.ts // contains utility functions for loading/parsing Markdown
49 | ├── node_modules
50 | ├── tsconfig.json
51 | ├── package.json
52 | ├── next.config.js
53 | ├── next-env.d.ts
54 | ├── .gitignore
55 |
56 |
57 | Both React and TypeScript are baked into the DNA of Next.js, so you get these for free when you set up a Next.js project.
59 |Gatsby, on the other hand, has a special plugin for TypeScript support, but it's not officially supported and seems to be low on their priority list. Also, after messing with it for an hour I couldn't get it to play nice with hot reload.
60 |Using Next's special getStaticProps
hook and glorious dynamic imports, it's trivial to a Markdown file and pass its contents into your React components as a prop. This achieves the holy grail I was searching for: the ability to easily mix React and Markdown.
Every Markdown file can include a "frontmatter block" containing metadata. I implemented a simple utility function (loadPost
) that loads a Markdown file, parses its contents, and returns a TypeScript object with the following signature:
type PostData = {
65 | path: string; // the relative URL to this page, can be used as an href
66 | content: string; // the body of the MD file
67 | title?: string;
68 | subtitle?: string;
69 | date?: number;
70 | author?: string;
71 | authorPhoto?: string;
72 | authorTwitter?: string;
73 | tags?: string[];
74 | bannerPhoto?: string;
75 | thumbnailPhoto?: string;
76 | };
77 |
78 | I implemented a separate function loadPosts
that loads all the Markdown files under /md/blog
and returns them as an array (PostData[]
). I use loadPosts
on this site's home page to render a list of all posts I've written.
I used the wonderful react-markdown
package to render Markdown as a React component. My Markdown rendered component (/components/Markdown.tsx
) provides some default styles inspired by Medium's design. Just modify the style
pros in Markdown.tsx
to customize the design to your liking.
You can easily drop code blocks into your blog posts using triple-backtick syntax. Specify the programming language with a "language tag", just like GitHub!
83 |To achieve this I implemented a custom code
renderer (/components/Code.tsx
) for react-markdown
that uses react-syntax-highlighter to handle the highlighting. So this:
90 |// pretty neat huh? 86 | const test = (arg: string) => { 87 | return arg.length > 5; 88 | }; 89 |
turns into this:
91 |// pretty neat huh?
92 | const test = (arg: string) => {
93 | return arg.length > 5;
94 | };
95 |
96 | An RSS feed is auto-generated from your blog post feed. This feed is generated using the rss
module (for converting JSON to RSS format) and showdown
for converting the markdown files to RSS-compatible HTML. The feed is generated during the build step and written as a static file to /rss.xml
in your static assets folder. It's dead simple. That's the joy of being able to easily write custom build scripts on top of Next.js's getStaticProps
hooks!
Every blog post page automatically populated meta tags based on the post metadata. This includes a title
tag, meta
tags, og:
tags, Twitter metadata, and a link
tag containing the canonical URL. You can modify/augment this in the PostMeta.ts
component.
You can generate a fully static version of your site using yarn build && yarn export
. This step is entirely powered by Next.js. The static site is exported to the out
directory.
After its generated, use your static file hosting service of choice (Firebase Hosting, Vercel, Netlify) to deploy your site.
103 |There's nothing "under the hood" here. You can view and modify all the files that provide the functionality described above. Devii just provides a project scaffold, some Markdown-loading loading utilities (in loader.ts
), and some sensible styling defaults (especially in Markdown.tsx
).
To start customizing, modify index.tsx
(the home page), Essay.tsx
(the blog post template), and Markdown.tsx
(the Markdown renderer).
Head to the GitHub repo to get started: https://github.com/colinhacks/devii. If you like this project, leave a ⭐️star⭐️ to help more people find Devii! 😎
108 |To jump straight into the code, clone the repo and start the development server like so:
109 |git clone git@github.com:colinhacks/devii.git mysite
110 | cd mysite
111 | yarn
112 | yarn dev
113 |
]]>/md/blog/test.md
.
114 | Devii is a starter kit for building a personal website with the best tools 2020 has to offer.
115 |/md/blog
to add a new post to your blog!title
, subtitle
, datePublished
(timestamp), author
, authorPhoto
, and bannerPhoto
.Markdown.tsx
) contains default styles inspired by Medium.yarn build && yarn export
. Powered by Next.js. // pretty neat huh?
124 | const test: (arg: string) => boolean = (arg) => {
125 | return arg.length > 5;
126 | };
127 |
128 | index.tsx
(the home page), BlogPost.tsx
(the blog post template), and Markdown.tsx
(the Markdown renderer). And of course you can add entirely new pages as well!Head to the GitHub repo to get started: https://github.com/colinhacks/devii. If you like this project, leave a ⭐️star⭐️ to help more people find Devii 😎
]]>