もともとメモ書きとかをどうやって処理するかを考えていて、Xでもいいかなとおもったり、他の方法でもいいかなとかおもったりもしたんですが、相談しつつもできそうのなので、実装した感じです。
- トップページや読者のフィードが「薄い記事」で埋め尽くされる。
- 過去に書いた渾身の長文記事がスクロールの彼方へ消え去り、視認性が低下する。
- SEOの観点でも「Thin Content(内容の薄いページ)」が量産されることで、サイト全体の評価(ドメインパワー)を下げるリスクがある。
ということでサイドバーに構築した次第です。
技術スタックと事前要件
- プラットフォーム: はてなブログ(Pro版推奨)
- 言語/技術: HTML, CSS, Vanilla JavaScript (ES6+), DOM API (MutationObserver, Fetch API)
- データソース: はてなブログ標準組み込みのカテゴリ別RSSフィード
開発の変遷(失敗パターンとその乗り越え方)
このシステムの目標は「通常の記事フィードからはShort Noteを消し、サイドバー(ログ用UI)にだけ表示させる」。ただ失敗が多い。
失敗1:単純にCSSで display: none するだけ
一番最初に試したのは、記事一覧画面で「ShortNote」というカテゴリを持つ要素をCSSで非表示にする
問題点(フリッカー現象)はてなブログのテーマによっては、記事カードが非同期で後から読み込まれることがあります。そのため、画面を開いた瞬間に一瞬だけShortNoteの記事が見え、その0.1秒後にフワッと消えるという、極めて見栄えの悪い「表示のチラつき(フリッカー)」が発生。
失敗2:
DOMContentLoaded(ページ読み込み完了時)にJSで消す次に、ページの読み込みが終わった瞬間にJavaScriptを走らせて要素を抹殺するアプローチとる。
問題点(レースコンディション): 近年のWebサイトは遅延読み込み(Lazy Load)や無限スクロールが当たり前です。パッチが走った「後」に新しく生成された記事カードにはフィルターがかからず、結局トップページにShortNoteが復活した。
実際の実装
処理フロー(システム構成図)
- [防壁1] 物理遮断(CSS): レンダリング前に最速で一次ブロック。
- [防壁2] 持続監視(MutationObserver): 後から非同期で生成される要素を徹底的に追跡・抹殺。さらに個別ページには
noindexを自動付与しSEOを守る。 - [再定義] 情報の抽出(RSS Fetch API): 裏でRSSを叩き、サイドバーに独自のUIとして最新ログを描画する。
実際の実装コード
同じ仕組みを導入したい方は、以下のコードをご自身の環境に合わせて配備してください。
1. 装飾層(デザインCSSへの追記)
ロードされた瞬間に光速でDOMノードの描画を停止しつつ、サイドバーの独自ウィジェットを装飾します。
/* トップページからShortNote記事を物理的に消す */ body.page-index .category-ShortNote { display: none !important; } /* Sidebar ShortNote Widget (Recent Logs) */ .tech-short-note-widget { background: #fff; border: 1px solid #e0e0e0; border-radius: 6px; overflow: hidden; margin-bottom: 2rem; } .tech-short-note-header { background: #1a355e; padding: 10px 15px; display: flex; justify-content: space-between; align-items: center; color: #fff; } .tech-short-note-title { font-size: 0.85rem; font-weight: bold; margin: 0; } .tech-short-note-pulse { width: 8px; height: 8px; background: #4caf50; border-radius: 50%; box-shadow: 0 0 5px #4caf50; } .tech-short-note-item { display: block; padding: 12px 15px; border-bottom: 1px solid #eee; text-decoration: none; color: #333; transition: 0.2s; } .tech-short-note-item:hover { background: #f8f9fa; border-left: 4px solid #1a355e; padding-left: 11px; } .tech-short-note-date { font-size: 0.7rem; color: #888; font-weight: 700; } .tech-short-note-subject { font-size: 0.85rem; font-weight: bold; margin: 4px 0; line-height: 1.4; color: #1a355e; } .tech-short-note-excerpt { font-size: 0.75rem; color: #666; margin: 0; line-height: 1.5; }
2. 統合防御層(ヘッダ > タイトル下 に配置)
「MutationObserver」を用いて、後から読み込まれる記事カードも逃さず非表示にします。同時に、ShortNoteの個別記事ページには自動で noindex メタタグを仕込みます。
<script> (function() { const runFilter = () => { // 1. [SEO防壁] 個別記事に noindex を自動付与 if (document.body.classList.contains('page-entry')) { if (Array.from(document.querySelectorAll('.entry-categories a')).some(c => c.textContent.trim() === 'ShortNote')) { if (!document.querySelector('meta[name="robots"]')) { const m = document.createElement('meta'); m.name = 'robots'; m.content = 'noindex, follow'; document.head.appendChild(m); } } } // 2. [UI防壁] トップページ & 最新記事からの完全排除 const snTitles = Array.from(document.querySelectorAll('.tech-short-note-subject')).map(el => el.textContent.trim()); const targets = document.querySelectorAll('article, .entry, .archive-entry, .hatena-module-recent-entries li, [class*="entry-card"]'); targets.forEach(el => { // カテゴリにShortNoteが含まれる、または抽出したRSSのタイトルと一致すれば消す const hasCat = !!el.querySelector('a[href*="category/ShortNote"]'); const titleText = (el.querySelector('.entry-title-link, .urllist-title-link, .entry-title')?.textContent || "").trim(); const isSNTitle = snTitles.includes(titleText); if (hasCat || isSNTitle) { if (el.style.display !== 'none') el.style.display = 'none'; } }); }; runFilter(); // 3. 非同期読み込みを持続監視(ここが要) new বৃহত্তর MutationObserver(runFilter).observe(document.body, { childList: true, subtree: true }); })(); </script>
3. 表示層(サイドバーの「HTMLモジュール」に配置)
除外したメモを、美しい独自UIとしてサイドバーに復元します。タイトルは空でも構いません。
<aside class="tech-short-note-widget"> <div class="tech-short-note-header"> <h3 class="tech-short-note-title">Recent Logs</h3> <span class="tech-short-note-pulse" title="System Active"></span> </div> <div id="dynamic-short-note-list"> <!-- ローディング表示 --> <div style="padding:15px;font-size:0.8rem;color:#999;">Initializing...</div> </div> </aside> <script> (function() { const rssUrl = '/rss/category/ShortNote'; // 自身のブログのカテゴリRSSのURL const container = document.getElementById('dynamic-short-note-list'); const esc = (s) => s.replace(/[&'`"<>]/g, (m) => ({'&':'&',"'":"'",'`':"`",'"':""",'<':"<",'>':">"}[m])); // Fetch APIでRSSを裏から取得してパース fetch(rssUrl).then(r => r.ok ? r.text() : Promise.reject()).then(str => { const data = new window.DOMParser().parseFromString(str, "text/xml"); const items = data.querySelectorAll("item"); if (!items.length) { container.innerHTML = '<div style="padding:15px;font-size:0.8rem;">No logs.</div>'; return; } let html = ""; // 最新3件だけを抽出してリスト化 for (let i = 0; i < Math.min(3, items.length); i++) { const title = esc(items[i].querySelector("title")?.textContent || ""); const link = items[i].querySelector("link")?.textContent || "#"; const date = new Date(items[i].querySelector("pubDate").textContent).toISOString().split('T')[0]; let desc = esc(items[i].querySelector("description")?.textContent.replace(/<[^>]+>/g, '').trim() || "").substring(0, 40) + "..."; html += `<a href="${link}" class="tech-short-note-item"><div class="tech-short-note-date">${date}</div><div class="tech-short-note-subject">${title}</div><p class="tech-short-note-excerpt">${desc}</p></a>`; } container.innerHTML = html; }).catch(() => { container.innerHTML = '<div style="padding:15px;color:#d32f2f;">Load error.</div>'; }); })(); </script>

こういうのができました。
これで細かいことをメモ帳ができました。
導入結果
- メインフィードの完全な衛生管理: トップページには「書き上げた長文の記事」。
- サイドバーの活性化(動的アクセント): 逆にサイドバーには常に「現在進行形での試行錯誤やエラー解決」のメモが流れる。
- 精神的デッドロックの解除: 短いメモを流す場所ができあがった。どうでもいいのもながせる。
まとめ
これでとりあえずメモのたまり場になる部分ができた。
基本的には考えとしては200文字のメモ帳と同じなので、やりやすいはず(運用しないとわからない)