Linyu’s Light Years的诞生

date
Feb 13, 2025
slug
born
status
Published
tags
Design
Tech
summary
How to build a personal blog
type
Newsletter

写在前面

一直以来我没太有建立个人博客的想法。但是奈何身边的朋友一再吐槽我那糟糕的分享欲,我觉得个人博客作为一个隐私性独立性较好的平台可以作为“第一步”。因此Linyu’s Light Years的建立正式提上日程。一方面作为对外分享的窗口,另一方面可以作为自我反思的渠道之一。有的人说个人博客不应该成为一种压力,而应该是作为发泄自己分享欲的媒介。不过我确实比较特殊,我想个人博客对我来说更像是一种推着我向前的无形压力吧(毕竟这个域名就爆了不少金币呢)。
个人博客的搭建过程本不想写成一篇博客,但不曾想我搭建这个项目的过程中用到的开源项目的官方文档离奇消失了(其实是作者的域名过期了)。
notion imagenotion image
尝试联系无果之后只得自己修改项目源代码,过程真是不堪回首(奈何这个主题我实在是喜欢呢)。于是,我决定简单记录一下搭建过程,一方面作为后续修改个人博客的参照,另一方面也给想要使用notion搭建个人博客的人多一种选择吧。

调研阶段

建立个人博客的开源技术栈有许多种。笔者作为notion孝子,选择技术栈的过程中优先选择能够和Notion联动的建站方式。期间多多少少调研了不少技术栈,下面按照时间顺序盘点一下。
🤩
市面上存在许多Notion现成建站工具,确实可以省下一些力气,但是相应地自定制程度极低,大部分重要功能都需要付费使用。作为一个会写一点前端代码的建站小白,我最终还是没有探索这些工具。
我主要探索了下面几种Notion建站工具的使用

NotionNext

notion imagenotion image
这是我部署好的第一版本网站,也是我尝试的第一个Notion建站工具。
先说优点:
  • 节目美观,有多个主题可供选择,实力足够的情况下可以自己写一个主题
  • 官方文档详细
  • 社区活跃,仓库直到我写这篇文章的时候仍然在持续更新
  • 拥有相对比较丰富的功能(如博客加锁、制订博客icon、访客统计等等),相比于其他方案来说UI细节处理得更好
再说缺点:
  • 页面加载速度有限、性能不太好
  • 项目庞大,Hack难度相对较大(这算缺点嘛???)

Nobelium

notion imagenotion image
这是我第二次尝试的网站,
优点:
  • 页面加载性能快
  • 风格简约,Hack难度相对较低(网上有人基于Nobelium定制了自己的个人博客网站)
    • Nobelium开发者基于Nobelium定制的个人博客(有点帅啊,项目地址:
      notion imagenotion image
  • 官方文档齐全
  • 项目相对成熟,虽然仓库已经11个月没有更新过了,但是受众广泛(3k Stars,2k Forks)
缺点:
  • UI过于简约、不够美观
  • 页面功能少

Notionic

notion imagenotion image
最后的尝试留给了Notionic
优点:
  • UI简约同时又不会缺失美观性
  • 可以和Craft联动(IOS用户狂喜)
  • 页面加载流畅,性能达标
  • 自带和博客风格匹配的评论区
  • 和Telegram联动,通过Telegram Bot将博客的留言自动同步到Telegram中
  • 源码相对简单(从作者提交次数粗略看出),容易上手修改
缺点:
  • 社区不是那么活跃,仓库上次更新已经是在两年前,使用者也相对少(295 Stars,250 Forks)
  • 官方文档已经不可访问了(因为作者的域名到期了)
 
综合上面所有优缺点,我选择了兼顾评论区、性能、美观程度的Notionic(加上当时Notionic的官方文档尚且“活着”)。下面记录一下Notionic如何修改成Linyu’s Light Years吧。

实践阶段

整个博客部署在vercel平台上,类似于NotionNext的部署方式(官方文档:https://docs.tangly1024.com/article/vercel-notionnext-notion)。

个人信息完善

将Notionic Fork过来之后我们需要修改部分配置(blog.config.js)
  • title :博客标题
  • author :博客作者
  • email :你的邮箱
  • link :你的域名(这里没填好的话后面配置评论区会出问题)
  • description :你的个性签名(最好是中二发言🐶)
  • since :博客诞生时间
  • pagesShow :哪些页面需要展示,哪些些页面不用
  • socialLink :你的社交账号网址
我平时并不使用telegram和twitter,因此这两个部分我都在源代码中删除掉了,这里的配置也就不记载了
🚫
注:下面的配置代码可以只关注我写的这些,其他可以先不修改,不影响使用
const BLOG = { title: 'Notionic', author: '左蓝', email: 'i@zuolan.me', link: 'https://zuolan.me', newsletter: 'Notionic Weekly', description: 'A static blog build on top of Notion and Next.js', lang: 'en-US', // ['en-US', 'zh-CN', 'zh-HK', 'zh-TW', 'ja-JP', 'es-ES'] timezone: 'Asia/Shanghai', // See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for all options. appearance: 'auto', // ['light', 'dark', 'auto'], font: 'sans-serif', // ['sans-serif', 'serif'] lightBackground: '#F6F8FA', // use hex value, don't forget '#' e.g #fffefc darkBackground: '#212936', // use hex value, don't forget '#' path: '', // leave this empty unless you want to deploy Notionic in a folder since: 2022, // If leave this empty, current year will be used. postsPerPage: 10, sortByDate: true, pagesShow: { newsletter: true, notes: true, projects: true, contact: true, books: true, friends: true }, showWeChatPay: true, previewImagesEnabled: true, autoCollapsedNavBar: false, // The automatically collapsed navigation bar ogImageGenerateHost: 'og-zl.vercel.app', // The link to generate OG image, don't end with a slash defaultCover: '/cover.jpg', socialLink: { twitter: 'https://twitter.com/izuolan', github: 'https://github.com/izuolan', telegram: 'https://t.me/zuolan' }, seo: { keywords: ['Notionic', 'Zuolan', 'Blog'], googleSiteVerification: '' // Remove the value or replace it with your own google site verification code }, notionPageId: process.env.NOTION_PAGE_ID, // DO NOT CHANGE THIS! Edit .env file! notionSpacesId: process.env.NOTION_SPACES_ID, // DO NOT CHANGE THIS! Edit .env file! notionAccessToken: process.env.NOTION_ACCESS_TOKEN, // Useful if you prefer not to make your database public notionDomain: 'izuolan.notion.site', telegramToken: process.env.TELEGRAM_TOKEN, // The token of your Telegram bot telegramChatId: '263895784', // The chat id of your Telegram bot telegramChannelUrl: 'https://channel.zuolan.me/', // The link of your Telegram channel telegramChannelName: 'zuolan_me', // The name of your Telegram channel craftConfigShareUrl: 'https://www.craft.do/s/kQtcWqkv98cHhB', // The link to share your craft config analytics: { provider: '', // Currently we support Google Analytics, Ackee, Umami and Cloudflare Insights, please fill with 'ga' or 'ackee' or 'umami' or 'cf', leave it empty to disable it. ackeeConfig: { tracker: '', // e.g 'https://ackee.example.com/tracker.js' dataAckeeServer: '', // e.g https://ackee.example.com , don't end with a slash domainId: '' // e.g '0e2257a8-54d4-4847-91a1-0311ea48cc7b' }, cfConfig: { scriptUrl: 'https://static.cloudflareinsights.com/beacon.min.js', // Default token: '' // Like '{"token": "xxxxxxxxxxxxxxxxxx"}' }, gaConfig: { measurementId: '' // e.g: G-XXXXXXXXXX }, umamiConfig: { scriptUrl: '', // The url of your Umami script websiteId: '' // The website id of your Umami instance } }, comment: { // support provider: utterances, supacomments provider: '', // leave it empty if you don't need any comment plugin supaCommentsConfig: { supabaseUrl: '', // The url of your Supabase instance supabaseAnonKey: '' // The anonymous key of your Supabase instance }, utterancesConfig: { repo: '' } }, isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) } // export default BLOG module.exports = BLOGconst BLOG = { title: 'Notionic', author: '左蓝', email: 'i@zuolan.me', link: 'https://zuolan.me', newsletter: 'Notionic Weekly', description: 'A static blog build on top of Notion and Next.js', lang: 'en-US', // ['en-US', 'zh-CN', 'zh-HK', 'zh-TW', 'ja-JP', 'es-ES'] timezone: 'Asia/Shanghai', // See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for all options. appearance: 'auto', // ['light', 'dark', 'auto'], font: 'sans-serif', // ['sans-serif', 'serif'] lightBackground: '#F6F8FA', // use hex value, don't forget '#' e.g #fffefc darkBackground: '#212936', // use hex value, don't forget '#' path: '', // leave this empty unless you want to deploy Notionic in a folder since: 2022, // If leave this empty, current year will be used. postsPerPage: 10, sortByDate: true, pagesShow: { newsletter: true, notes: true, projects: true, contact: true, books: true, friends: true }, showWeChatPay: true, previewImagesEnabled: true, autoCollapsedNavBar: false, // The automatically collapsed navigation bar ogImageGenerateHost: 'og-zl.vercel.app', // The link to generate OG image, don't end with a slash defaultCover: '/cover.jpg', socialLink: { twitter: 'https://twitter.com/izuolan', github: 'https://github.com/izuolan', telegram: 'https://t.me/zuolan' }, seo: { keywords: ['Notionic', 'Zuolan', 'Blog'], googleSiteVerification: '' // Remove the value or replace it with your own google site verification code }, notionPageId: process.env.NOTION_PAGE_ID, // DO NOT CHANGE THIS! Edit .env file! notionSpacesId: process.env.NOTION_SPACES_ID, // DO NOT CHANGE THIS! Edit .env file! notionAccessToken: process.env.NOTION_ACCESS_TOKEN, // Useful if you prefer not to make your database public notionDomain: 'izuolan.notion.site', telegramToken: process.env.TELEGRAM_TOKEN, // The token of your Telegram bot telegramChatId: '263895784', // The chat id of your Telegram bot telegramChannelUrl: 'https://channel.zuolan.me/', // The link of your Telegram channel telegramChannelName: 'zuolan_me', // The name of your Telegram channel craftConfigShareUrl: 'https://www.craft.do/s/kQtcWqkv98cHhB', // The link to share your craft config analytics: { provider: '', // Currently we support Google Analytics, Ackee, Umami and Cloudflare Insights, please fill with 'ga' or 'ackee' or 'umami' or 'cf', leave it empty to disable it. ackeeConfig: { tracker: '', // e.g 'https://ackee.example.com/tracker.js' dataAckeeServer: '', // e.g https://ackee.example.com , don't end with a slash domainId: '' // e.g '0e2257a8-54d4-4847-91a1-0311ea48cc7b' }, cfConfig: { scriptUrl: 'https://static.cloudflareinsights.com/beacon.min.js', // Default token: '' // Like '{"token": "xxxxxxxxxxxxxxxxxx"}' }, gaConfig: { measurementId: '' // e.g: G-XXXXXXXXXX }, umamiConfig: { scriptUrl: '', // The url of your Umami script websiteId: '' // The website id of your Umami instance } }, comment: { // support provider: utterances, supacomments provider: '', // leave it empty if you don't need any comment plugin supaCommentsConfig: { supabaseUrl: '', // The url of your Supabase instance supabaseAnonKey: '' // The anonymous key of your Supabase instance }, utterancesConfig: { repo: '' } }, isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) } // export default BLOG module.exports = BLOG

个人LOGO完善

同时我们还需要修改这几个位置,分别是
notion imagenotion image

首先,需要先设计一个自己的LOGO,分别保留svg和png格式,分别替代项目中的favicon.svg和favicon.ico和favicon.png。
接下来打开Logo.js,将之前svg格式的LOGO代码复制到Logo.js的Logo对象中。但是自己设计的LOGO可能需要微调一些位置属性,我采用的方法是通过F12进入控制台通过调整svg代码中的位置属性和大小属性直到符合要求,然后将对应属性更新到代码仓库中。

删除不必要的社交平台展示

由于笔者平时实在不会使用telegram和twitter,因此我打算把和这些社交媒体绑定的相关功能全部删除(或者替代)。
这个部分目标主要分为两部分
删除twitter和telegram的图标
将contact功能自动转发给telegram的逻辑进行修改

第一个目标

第一个目标已经完成
我发现这两个应用的UI展示通过Social.js文件定义。于是将Social.js中和这两个应用相关的部分全部注释掉(全局索引秒了🥳)
除了主页以外,Newsletter模块也会展示这两个社交媒体的链接,而这个模块中这两个应用的UI展示定义在Newsletter.js中,我们类似地将其中的配置注释掉即可。
notion imagenotion image
至此,红框标出的两个图标就消失不见啦 😜!

第二个目标

第二个目标现在还没有完成。一方面是还没设计好,另一方面是最近开始忙碌起来了,先留个伏笔吧。
先更新自己目前做的:
这是修改前
notion imagenotion image
这是修改后
notion imagenotion image
将通过telegram这句话删除掉了,这句话定义在lang.js
notion imagenotion image

评论区搭建

其实说来也不难,只要搞定两个部分就可以了
database(用来存储用户评论)
前端用来调用database的api

数据库方面

database我们采用supabase提供的Free Plan,就以笔者博客目前的浏览量来说这是完全足够的。。。在创建好数据库后,我们需要创建一个表用来存储用户评论,创建的表结构如下
👉🏼
table name: comments
{
id: int8,
name: text,
email: text,
postURL: text,
comment: text,
created_at: timestamptz,
show: bool,
}
其中id为主键
同时,我们需要修改数据库的一些权限设置限制外部应用通过api能够操作的数据和数据操作的方式(需要设置RLS)。不过根据官方文档说明,采用Table Editor创建的表是自带RLS的,使用SQL Editor创建的表则需要执行额外的SQL来确保RLS。
alter table <schema_name>.<table_name> enable row level security;
接着我创建了一些权限相关的policy。进入新建数据库项目页面后,选择Authentication选项,接着进入policies页面,创建相关policy,我分别创建了读和写的权限,主要是限制每条评论的长度。(也是为了以后如果需要增加policy做一些准备)。

前端代码方面

前端代码通过向特定的api发送网络请求进行数据库操作,那么就需要我们将api和必要的key告诉前端代码。这一部分的设置仍然被放在了blog.config.js中
comment: { // support provider: utterances, supacomments provider: 'supacomments', // leave it empty if you don't need any comment plugin supaCommentsConfig: { supabaseUrl: '', // The url of your Supabase instance supabaseAnonKey: '' // The anonymous key of your Supabase instance }, utterancesConfig: { repo: '' } },
那么supabaseUrl和supabaseAnonKey都是可以从supabase中的项目中得到的信息,将这些信息填好之后一个评论区就搭建好了。
😜
完成这些代码修改之后将代码push到GitHub上之后,vercel会自动构建这个项目并部署
对于本文内容有任何疑问, 可与我联系.