使用Cloudflare和notion搭建自己的博客

欢迎访问我的个人博客ximikang.icu

使用Cloudflare和notion搭建自己的博客

使用过Nation的小伙伴一定在某个时刻冒出过这样子的想法:这个笔记应用这么好用,而且是基于Web端开发的,那为什么不可以用他来作为我自己的博客呢?这样我是不是可以用记笔记的方式来快速的实现和维护自己的博客了呢?

我搭建的个人博客,欢迎大家访问。https://notion.ximikang.icu/

Untitled

购买一个域名并将域名转移到Cloudflare

首先我们需要将域名的域名服务器设置为 Cloudflare 的域名服务器,这样就可以使用 Cloudflare 的 DNS 服务了。

注册Cloudflare ,然后在这里添加自己的域名:

Untitled

将你的域名指向notion

切换到 dns 页面,添加一条 CNAME 记录,如果你自己的二级域名为 blog.xxx.com,那么名称那就填 blog,目标填 Notion 的域名,保存。

Untitled

Untitled

或者需要将主域名设置为则为

Untitled

注意需要将代理状态切换至已代理,这样才能用cloudflare的免费CDN。

添加后下方就会显示已添加的域名,点击已添加的域名进入设置页面。

配置Cloudflare的Web Worker

配置workder使得我们访问我们上面定义的http请求可以自动改写。

Untitled

  1. 首先管理Workers,新建一个对应的notion的worker。

    Untitled

    获取notion页面中的链接ID,如图中的id需要在worker配置中使用。

    Untitled

    创建worker后编辑worker,将如下代码复制到worker中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    /* CONFIGURATION STARTS HERE */

    /* 添加域名 notion.ximikang.icu*/

    const MY_DOMAIN = '';

    /*
    添加刚才的id
    例如:
    '': 'bb1678c7e8fe47b29bf49aac08aebbb',
    */
    const SLUG_TO_PAGE = {
    '': '',
    };

    /* 添加主页面的标题和描述 */
    const PAGE_TITLE = '';
    const PAGE_DESCRIPTION = '';

    /* 可以选择显示的字体 https://fonts.google.com
    例如:const GOOGLE_FONT = 'Noto Sans Simplified Chinese';*/
    const GOOGLE_FONT = '';

    /* 添加自定义脚本,可以添加Google Analytics */
    const CUSTOM_SCRIPT = ``;

    /* CONFIGURATION ENDS HERE */

    const PAGE_TO_SLUG = {};
    const slugs = [];
    const pages = [];
    Object.keys(SLUG_TO_PAGE).forEach(slug => {
    const page = SLUG_TO_PAGE[slug];
    slugs.push(slug);
    pages.push(page);
    PAGE_TO_SLUG[page] = slug;
    });

    addEventListener('fetch', event => {
    event.respondWith(fetchAndApply(event.request));
    });

    function generateSitemap() {
    let sitemap = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
    slugs.forEach(
    (slug) =>
    (sitemap +=
    '<url><loc>https://' + MY_DOMAIN + '/' + slug + '</loc></url>')
    );
    sitemap += '</urlset>';
    return sitemap;
    }

    const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
    };

    function handleOptions(request) {
    if (request.headers.get('Origin') !== null &&
    request.headers.get('Access-Control-Request-Method') !== null &&
    request.headers.get('Access-Control-Request-Headers') !== null) {
    // Handle CORS pre-flight request.
    return new Response(null, {
    headers: corsHeaders
    });
    } else {
    // Handle standard OPTIONS request.
    return new Response(null, {
    headers: {
    'Allow': 'GET, HEAD, POST, PUT, OPTIONS',
    }
    });
    }
    }

    async function fetchAndApply(request) {
    if (request.method === 'OPTIONS') {
    return handleOptions(request);
    }
    let url = new URL(request.url);
    url.hostname = 'www.notion.so';
    if (url.pathname === '/robots.txt') {
    return new Response('Sitemap: https://' + MY_DOMAIN + '/sitemap.xml');
    }
    if (url.pathname === '/sitemap.xml') {
    let response = new Response(generateSitemap());
    response.headers.set('content-type', 'application/xml');
    return response;
    }
    let response;
    if (url.pathname.startsWith('/app') && url.pathname.endsWith('js')) {
    response = await fetch(url.toString());
    let body = await response.text();
    response = new Response(body.replace(/www.notion.so/g, MY_DOMAIN).replace(/notion.so/g, MY_DOMAIN), response);
    response.headers.set('Content-Type', 'application/x-javascript');
    return response;
    } else if ((url.pathname.startsWith('/api'))) {
    // Forward API
    response = await fetch(url.toString(), {
    body: url.pathname.startsWith('/api/v3/getPublicPageData') ? null : request.body,
    headers: {
    'content-type': 'application/json;charset=UTF-8',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'
    },
    method: 'POST',
    });
    response = new Response(response.body, response);
    response.headers.set('Access-Control-Allow-Origin', '*');
    return response;
    } else if (slugs.indexOf(url.pathname.slice(1)) > -1) {
    const pageId = SLUG_TO_PAGE[url.pathname.slice(1)];
    return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 301);
    } else {
    response = await fetch(url.toString(), {
    body: request.body,
    headers: request.headers,
    method: request.method,
    });
    response = new Response(response.body, response);
    response.headers.delete('Content-Security-Policy');
    response.headers.delete('X-Content-Security-Policy');
    }

    return appendJavascript(response, SLUG_TO_PAGE);
    }

    class MetaRewriter {
    element(element) {
    if (PAGE_TITLE !== '') {
    if (element.getAttribute('property') === 'og:title'
    || element.getAttribute('name') === 'twitter:title') {
    element.setAttribute('content', PAGE_TITLE);
    }
    if (element.tagName === 'title') {
    element.setInnerContent(PAGE_TITLE);
    }
    }
    if (PAGE_DESCRIPTION !== '') {
    if (element.getAttribute('name') === 'description'
    || element.getAttribute('property') === 'og:description'
    || element.getAttribute('name') === 'twitter:description') {
    element.setAttribute('content', PAGE_DESCRIPTION);
    }
    }
    if (element.getAttribute('property') === 'og:url'
    || element.getAttribute('name') === 'twitter:url') {
    element.setAttribute('content', MY_DOMAIN);
    }
    if (element.getAttribute('name') === 'apple-itunes-app') {
    element.remove();
    }
    }
    }

    class HeadRewriter {
    element(element) {
    if (GOOGLE_FONT !== '') {
    element.append(`<link href="https://fonts.googleapis.com/css?family=${GOOGLE_FONT.replace(' ', '+')}:Regular,Bold,Italic&display=swap" rel="stylesheet">
    <style>* { font-family: "${GOOGLE_FONT}" !important; }</style>`, {
    html: true
    });
    }
    element.append(`<style>
    div.notion-topbar > div > div:nth-child(3) { display: none !important; }
    div.notion-topbar > div > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(5) { display: none !important; }
    div.notion-topbar > div > div:nth-child(6) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(3) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; }
    div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; }
    </style>`, {
    html: true
    })
    }
    }

    class BodyRewriter {
    constructor(SLUG_TO_PAGE) {
    this.SLUG_TO_PAGE = SLUG_TO_PAGE;
    }
    element(element) {
    element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
    <script>
    window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}';
    const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
    const PAGE_TO_SLUG = {};
    const slugs = [];
    const pages = [];
    const el = document.createElement('div');
    let redirected = false;
    Object.keys(SLUG_TO_PAGE).forEach(slug => {
    const page = SLUG_TO_PAGE[slug];
    slugs.push(slug);
    pages.push(page);
    PAGE_TO_SLUG[page] = slug;
    });
    function getPage() {
    return location.pathname.slice(-32);
    }
    function getSlug() {
    return location.pathname.slice(1);
    }
    function updateSlug() {
    const slug = PAGE_TO_SLUG[getPage()];
    if (slug != null) {
    history.replaceState(history.state, '', '/' + slug);
    }
    }
    function onDark() {
    el.innerHTML = '<div title="Change to Light Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgb(46, 170, 220); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(12px) translateY(0px);"></div></div></div></div>';
    document.body.classList.add('dark');
    __console.environment.ThemeStore.setState({ mode: 'dark' });
    };
    function onLight() {
    el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgba(135, 131, 120, 0.3); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(0px) translateY(0px);"></div></div></div></div>';
    document.body.classList.remove('dark');
    __console.environment.ThemeStore.setState({ mode: 'light' });
    }
    function toggle() {
    if (document.body.classList.contains('dark')) {
    onLight();
    } else {
    onDark();
    }
    }
    function addDarkModeButton(device) {
    const nav = device === 'web' ? document.querySelector('.notion-topbar').firstChild : document.querySelector('.notion-topbar-mobile');
    el.className = 'toggle-mode';
    el.addEventListener('click', toggle);
    nav.appendChild(el);
    onLight();
    }
    const observer = new MutationObserver(function() {
    if (redirected) return;
    const nav = document.querySelector('.notion-topbar');
    const mobileNav = document.querySelector('.notion-topbar-mobile');
    if (nav && nav.firstChild && nav.firstChild.firstChild
    || mobileNav && mobileNav.firstChild) {
    redirected = true;
    updateSlug();
    addDarkModeButton(nav ? 'web' : 'mobile');
    const onpopstate = window.onpopstate;
    window.onpopstate = function() {
    if (slugs.includes(getSlug())) {
    const page = SLUG_TO_PAGE[getSlug()];
    if (page) {
    history.replaceState(history.state, 'bypass', '/' + page);
    }
    }
    onpopstate.apply(this, [].slice.call(arguments));
    updateSlug();
    };
    }
    });
    observer.observe(document.querySelector('#notion-app'), {
    childList: true,
    subtree: true,
    });
    const replaceState = window.history.replaceState;
    window.history.replaceState = function(state) {
    if (arguments[1] !== 'bypass' && slugs.includes(getSlug())) return;
    return replaceState.apply(window.history, arguments);
    };
    const pushState = window.history.pushState;
    window.history.pushState = function(state) {
    const dest = new URL(location.protocol + location.host + arguments[2]);
    const id = dest.pathname.slice(-32);
    if (pages.includes(id)) {
    arguments[2] = '/' + PAGE_TO_SLUG[id];
    }
    return pushState.apply(window.history, arguments);
    };
    const open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function() {
    arguments[1] = arguments[1].replace('${MY_DOMAIN}', 'www.notion.so');
    return open.apply(this, [].slice.call(arguments));
    };
    </script>${CUSTOM_SCRIPT}`, {
    html: true
    });
    }
    }

    async function appendJavascript(res, SLUG_TO_PAGE) {
    return new HTMLRewriter()
    .on('title', new MetaRewriter())
    .on('meta', new MetaRewriter())
    .on('head', new HeadRewriter())
    .on('body', new BodyRewriter(SLUG_TO_PAGE))
    .transform(res);
    }
  2. 配置对应的路由,也就是访问我们的域名后,需要执行我们定义的notion worker

    Untitled

    路由为我们的域名加上/*

    Worker为刚才新建的worker

    Untitled

    现在你的个人博客就大功告成了

添加Google Analytics脚本监控你的网站

进入Google Analytics后台 ,点击左上侧管理,选择需要添加代码的网站对应的账号和媒体资源后,依次点击媒体资源下的管理-数据流,可以看到如下所示界面:

Untitled

将如下的代码添加到worker中的自定义片段中就可以对你的博客进行访问监控。

Untitled

文章作者: ximikang
文章链接: http://ximikang.icu/2021/09/20/%E4%BD%BF%E7%94%A8Cloudflare%E5%92%8Cnotion%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E5%8D%9A%E5%AE%A2/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ximikang Blog