[{"data":1,"prerenderedAt":796},["ShallowReactive",2],{"content-/breadcrumb-unification":3,"all-pages-for-dir":794,"og-image-/breadcrumb-unification":795},{"id":4,"title":5,"body":6,"category":777,"description":778,"extension":779,"meta":780,"navigation":781,"ogImage":782,"path":783,"project_name":784,"published":785,"publishedAt":786,"seo":787,"stem":788,"tags":789,"todo":782,"unpublished":785,"updatedAt":782,"__hash__":793},"pages/2026-05/2026-05-09/breadcrumb-unification.md","全レッスンページのbreadcrumbを共通コンポーネント化して130ファイル一括変換",{"type":7,"value":8,"toc":764},"minimark",[9,13,26,33,36,41,52,60,63,121,124,437,447,451,462,465,663,666,670,693,696,699,727,730,744,747,757,760],[10,11,12],"h2",{"id":12},"はじめに",[14,15,16,17,21,22,25],"p",{},"ローカルでレッスンページを開き直したら、簿記3級スライドのパンクズには",[18,19,20],"code",{},"path","が抜けていてリンクが切れていた。会計入門は",[18,23,24],{},"Eurekapu / 講義ノート / 会計入門","と4階層、case100ノートは3階層、cockpitは独自レイアウトでLangSwitcherを抱え込んでいる。同じプロジェクトの中でパンクズの構造が10通りに分岐していた。",[14,27,28,29,32],{},"最初は「壊れている1ページだけ直そう」と思ったが、画面を行き来するうちに ",[18,30,31],{},"\u003Cnav>"," のマークアップが各ファイルにべた書きされているのが見え始め、共通化に踏み切った。",[10,34,35],{"id":35},"何をやったか",[37,38,40],"h3",{"id":39},"_1-まずb修正簿記3級スライドのpath追加","1. まずB修正：簿記3級スライドのpath追加",[14,42,43,44,47,48,51],{},"簿記3級スライドのパンクズだけ",[18,45,46],{},"\u003CNuxtLink>","の",[18,49,50],{},"to","が空になっていてクリックしても遷移しなかった。共通化の前に、まずこのバグだけ直してコミットした。",[37,53,55,56,59],{"id":54},"_2-共通コンポーネント-lessonbreadcrumbvue-を作成","2. 共通コンポーネント ",[18,57,58],{},"LessonBreadcrumb.vue"," を作成",[14,61,62],{},"10箇所のレイアウトを順に置換した：",[64,65,66,73,79,84,89,95,101,106,111,116],"ul",{},[67,68,69,72],"li",{},[18,70,71],{},"bookkeeping-miller","（簿記3級スライドのMillerレイアウト）",[67,74,75,78],{},[18,76,77],{},"note","（会計入門ノート）",[67,80,81],{},[18,82,83],{},"case100/NoteLayout",[67,85,86],{},[18,87,88],{},"case100/index",[67,90,91,94],{},[18,92,93],{},"cockpit-00-summary","（i18n + LangSwitcher を保持）",[67,96,97,100],{},[18,98,99],{},"cockpit-01-foundation","（4階層パンクズ）",[67,102,103],{},[18,104,105],{},"cashflow-statement/reference/index",[67,107,108],{},[18,109,110],{},"consolidated-accounting/index",[67,112,113],{},[18,114,115],{},"steps",[67,117,118],{},[18,119,120],{},"bookkeeping",[14,122,123],{},"cockpit系はLangSwitcherを右端に置いている関係でslotを切り、本文側のパンクズだけ共通化した。",[125,126,131],"pre",{"className":127,"code":128,"language":129,"meta":130,"style":130},"language-vue shiki shiki-themes vitesse-light vitesse-light","\u003C!-- LessonBreadcrumb.vue（核心のみ） -->\n\u003Ctemplate>\n  \u003Cnav class=\"lesson-breadcrumb\">\n    \u003CNuxtLink to=\"/\">Eurekapu\u003C/NuxtLink>\n    \u003Cspan>/\u003C/span>\n    \u003Ctemplate v-for=\"(item, i) in items\" :key=\"i\">\n      \u003CNuxtLink v-if=\"item.path\" :to=\"item.path\">{{ item.label }}\u003C/NuxtLink>\n      \u003Cspan v-else>{{ item.label }}\u003C/span>\n      \u003Cspan v-if=\"i \u003C items.length - 1\">/\u003C/span>\n    \u003C/template>\n    \u003Cslot name=\"trailing\" />\n  \u003C/nav>\n\u003C/template>\n","vue","",[18,132,133,142,156,184,219,236,292,334,354,382,392,418,428],{"__ignoreMap":130},[134,135,138],"span",{"class":136,"line":137},"line",1,[134,139,141],{"class":140},"sxvE3","\u003C!-- LessonBreadcrumb.vue（核心のみ） -->\n",[134,143,145,149,153],{"class":136,"line":144},2,[134,146,148],{"class":147},"shFtX","\u003C",[134,150,152],{"class":151},"sHkkW","template",[134,154,155],{"class":147},">\n",[134,157,159,162,165,169,172,176,180,182],{"class":136,"line":158},3,[134,160,161],{"class":147},"  \u003C",[134,163,164],{"class":151},"nav",[134,166,168],{"class":167},"s4oTP"," class",[134,170,171],{"class":147},"=",[134,173,175],{"class":174},"sMJiu","\"",[134,177,179],{"class":178},"sdGka","lesson-breadcrumb",[134,181,175],{"class":174},[134,183,155],{"class":147},[134,185,187,190,193,196,198,200,203,205,208,212,215,217],{"class":136,"line":186},4,[134,188,189],{"class":147},"    \u003C",[134,191,192],{"class":151},"NuxtLink",[134,194,195],{"class":167}," to",[134,197,171],{"class":147},[134,199,175],{"class":174},[134,201,202],{"class":178},"/",[134,204,175],{"class":174},[134,206,207],{"class":147},">",[134,209,211],{"class":210},"sG7-3","Eurekapu",[134,213,214],{"class":147},"\u003C/",[134,216,192],{"class":151},[134,218,155],{"class":147},[134,220,222,224,226,228,230,232,234],{"class":136,"line":221},5,[134,223,189],{"class":147},[134,225,134],{"class":151},[134,227,207],{"class":147},[134,229,202],{"class":210},[134,231,214],{"class":147},[134,233,134],{"class":151},[134,235,155],{"class":147},[134,237,239,241,243,246,248,250,253,256,259,262,265,269,272,274,277,281,283,285,288,290],{"class":136,"line":238},6,[134,240,189],{"class":147},[134,242,152],{"class":151},[134,244,245],{"class":151}," v-for",[134,247,171],{"class":147},[134,249,175],{"class":147},[134,251,252],{"class":147},"(",[134,254,255],{"class":167},"item",[134,257,258],{"class":147},",",[134,260,261],{"class":167}," i",[134,263,264],{"class":147},")",[134,266,268],{"class":267},"stQ0i"," in",[134,270,271],{"class":167}," items",[134,273,175],{"class":147},[134,275,276],{"class":147}," :",[134,278,280],{"class":279},"senZ8","key",[134,282,171],{"class":147},[134,284,175],{"class":147},[134,286,287],{"class":167},"i",[134,289,175],{"class":147},[134,291,155],{"class":147},[134,293,295,298,300,303,305,307,310,312,315,317,319,321,323,325,328,330,332],{"class":136,"line":294},7,[134,296,297],{"class":147},"      \u003C",[134,299,192],{"class":151},[134,301,302],{"class":167}," v-if",[134,304,171],{"class":147},[134,306,175],{"class":174},[134,308,309],{"class":178},"item.path",[134,311,175],{"class":174},[134,313,314],{"class":167}," :to",[134,316,171],{"class":147},[134,318,175],{"class":174},[134,320,309],{"class":178},[134,322,175],{"class":174},[134,324,207],{"class":147},[134,326,327],{"class":210},"{{ item.label }}",[134,329,214],{"class":147},[134,331,192],{"class":151},[134,333,155],{"class":147},[134,335,337,339,341,344,346,348,350,352],{"class":136,"line":336},8,[134,338,297],{"class":147},[134,340,134],{"class":151},[134,342,343],{"class":167}," v-else",[134,345,207],{"class":147},[134,347,327],{"class":210},[134,349,214],{"class":147},[134,351,134],{"class":151},[134,353,155],{"class":147},[134,355,357,359,361,363,365,367,370,372,374,376,378,380],{"class":136,"line":356},9,[134,358,297],{"class":147},[134,360,134],{"class":151},[134,362,302],{"class":167},[134,364,171],{"class":147},[134,366,175],{"class":174},[134,368,369],{"class":178},"i \u003C items.length - 1",[134,371,175],{"class":174},[134,373,207],{"class":147},[134,375,202],{"class":210},[134,377,214],{"class":147},[134,379,134],{"class":151},[134,381,155],{"class":147},[134,383,385,388,390],{"class":136,"line":384},10,[134,386,387],{"class":147},"    \u003C/",[134,389,152],{"class":151},[134,391,155],{"class":147},[134,393,395,397,400,403,405,407,410,412,416],{"class":136,"line":394},11,[134,396,189],{"class":147},[134,398,399],{"class":151},"slot",[134,401,402],{"class":167}," name",[134,404,171],{"class":147},[134,406,175],{"class":174},[134,408,409],{"class":178},"trailing",[134,411,175],{"class":174},[134,413,415],{"class":414},"s5V_Q"," /",[134,417,155],{"class":147},[134,419,421,424,426],{"class":136,"line":420},12,[134,422,423],{"class":147},"  \u003C/",[134,425,164],{"class":151},[134,427,155],{"class":147},[134,429,431,433,435],{"class":136,"line":430},13,[134,432,214],{"class":147},[134,434,152],{"class":151},[134,436,155],{"class":147},[14,438,439,442,443,446],{},[18,440,441],{},"items"," を配列で受け取り、最後尾を ",[18,444,445],{},"\u003Cslot name=\"trailing\" />"," にしておくと、cockpitのLangSwitcherを差し込める。",[37,448,450],{"id":449},"_3-case100ノート130ファイルを一括変換","3. case100ノート130ファイルを一括変換",[14,452,453,454,457,458,461],{},"case100ノートは1論点1ファイル、合計130ファイル。",[18,455,456],{},"topic-1","配下も同じ構造で、画面を開いてVS Codeで ",[18,459,460],{},"\u003Cnav class=\"breadcrumb\">"," を130回書き換えるのは現実的ではなかった。",[14,463,464],{},"スクリプトを書いて流した：",[125,466,470],{"className":467,"code":468,"language":469,"meta":130,"style":130},"language-ts shiki shiki-themes vitesse-light vitesse-light","// 既存のべた書きパンクズを \u003CLessonBreadcrumb :items=\"...\" /> に置換\nconst files = await glob('content/case100/**/*.vue')\nfor (const file of files) {\n  const src = await fs.readFile(file, 'utf8')\n  const next = src.replace(NAV_REGEX, (_, title) =>\n    `\u003CLessonBreadcrumb :items=\"[{ label: '講義ノート', path: '/case100' }, { label: '${title}' }]\" />`\n  )\n  await fs.writeFile(file, next)\n}\n","ts",[18,471,472,477,507,531,568,607,630,635,658],{"__ignoreMap":130},[134,473,474],{"class":136,"line":137},[134,475,476],{"class":140},"// 既存のべた書きパンクズを \u003CLessonBreadcrumb :items=\"...\" /> に置換\n",[134,478,479,482,485,488,491,494,496,499,502,504],{"class":136,"line":144},[134,480,481],{"class":267},"const ",[134,483,484],{"class":167},"files",[134,486,487],{"class":147}," =",[134,489,490],{"class":151}," await",[134,492,493],{"class":279}," glob",[134,495,252],{"class":147},[134,497,498],{"class":174},"'",[134,500,501],{"class":178},"content/case100/**/*.vue",[134,503,498],{"class":174},[134,505,506],{"class":147},")\n",[134,508,509,512,515,517,520,523,526,528],{"class":136,"line":158},[134,510,511],{"class":151},"for",[134,513,514],{"class":147}," (",[134,516,481],{"class":267},[134,518,519],{"class":167},"file",[134,521,522],{"class":267}," of",[134,524,525],{"class":167}," files",[134,527,264],{"class":147},[134,529,530],{"class":147}," {\n",[134,532,533,536,539,541,543,546,549,552,554,556,558,561,564,566],{"class":136,"line":186},[134,534,535],{"class":267},"  const ",[134,537,538],{"class":167},"src",[134,540,487],{"class":147},[134,542,490],{"class":151},[134,544,545],{"class":167}," fs",[134,547,548],{"class":147},".",[134,550,551],{"class":279},"readFile",[134,553,252],{"class":147},[134,555,519],{"class":167},[134,557,258],{"class":147},[134,559,560],{"class":174}," '",[134,562,563],{"class":178},"utf8",[134,565,498],{"class":174},[134,567,506],{"class":147},[134,569,570,572,575,577,580,582,585,587,590,592,594,597,599,602,604],{"class":136,"line":221},[134,571,535],{"class":267},[134,573,574],{"class":167},"next",[134,576,487],{"class":147},[134,578,579],{"class":167}," src",[134,581,548],{"class":147},[134,583,584],{"class":279},"replace",[134,586,252],{"class":147},[134,588,589],{"class":167},"NAV_REGEX",[134,591,258],{"class":147},[134,593,514],{"class":147},[134,595,596],{"class":167},"_",[134,598,258],{"class":147},[134,600,601],{"class":167}," title",[134,603,264],{"class":147},[134,605,606],{"class":147}," =>\n",[134,608,609,612,615,618,621,624,627],{"class":136,"line":238},[134,610,611],{"class":174},"    `",[134,613,614],{"class":178},"\u003CLessonBreadcrumb :items=\"[{ label: '講義ノート', path: '/case100' }, { label: '",[134,616,617],{"class":151},"${",[134,619,620],{"class":178},"title",[134,622,623],{"class":151},"}",[134,625,626],{"class":178},"' }]\" />",[134,628,629],{"class":174},"`\n",[134,631,632],{"class":136,"line":294},[134,633,634],{"class":147},"  )\n",[134,636,637,640,642,644,647,649,651,653,656],{"class":136,"line":336},[134,638,639],{"class":151},"  await",[134,641,545],{"class":167},[134,643,548],{"class":147},[134,645,646],{"class":279},"writeFile",[134,648,252],{"class":147},[134,650,519],{"class":167},[134,652,258],{"class":147},[134,654,655],{"class":167}," next",[134,657,506],{"class":147},[134,659,660],{"class":136,"line":356},[134,661,662],{"class":147},"}\n",[14,664,665],{},"130ファイルが10秒で書き変わった。derivativesディレクトリも同じ正規表現で当たるか確認したら、こちらは別レイアウトを使っていたので対象外と判定して除外した。",[37,667,669],{"id":668},"_4-別セッションのステージング衝突でやり直し","4. 別セッションのステージング衝突でやり直し",[14,671,672,673,676,677,680,681,684,685,688,689,692],{},"スクリプトを流したあと ",[18,674,675],{},"git status"," を見たら、別セッションで作業していた変更がステージング済みになっていた。",[18,678,679],{},"git stash"," で退避してから差分を再確認し、自分の担当分のみ",[18,682,683],{},"git add","して ",[18,686,687],{},"5bbe9bb"," でコミットした。「全部 ",[18,690,691],{},"git add ."," で済ませる」と他人の作業を巻き込むので、ファイル指定で足した。",[10,694,695],{"id":695},"ブラウザ確認",[14,697,698],{},"dev serverを立ち上げて各レイアウトを開いた：",[64,700,701,706,715,721],{},[67,702,703,705],{},[18,704,24],{}," — 3階層、リンクが効く",[67,707,708,711,712,714],{},[18,709,710],{},"Eurekapu / 講義ノート / 簿記3級"," — ",[18,713,20],{},"が入って簿記3級トップへ遷移",[67,716,717,720],{},[18,718,719],{},"Eurekapu / 講義ノート / cockpit / 第1章 基礎"," — 4階層、LangSwitcherが右端に残った",[67,722,723,726],{},[18,724,725],{},"Eurekapu / 講義ノート / case100 / 論点42 のれんの償却"," — 130ファイル全てでパンクズが表示",[10,728,729],{"id":729},"試行錯誤メモ",[64,731,732,735,738],{},[67,733,734],{},"最初は「該当ファイルだけ手で直す」案で進めたが、case100の中身を開いた瞬間に130ファイルあると気づいて方針転換した。1ファイル30秒でも65分かかる",[67,736,737],{},"スクリプト化したら10秒で終わった。手動で「あっこのファイル忘れてた」が発生する余地も消えた",[67,739,740,741,743],{},"別セッションのステージング衝突は予想外で、",[18,742,675],{}," を作業前と作業後で2回見るルールにした方がいい",[10,745,746],{"id":746},"学び",[14,748,749,750,752,753,756],{},"共通コンポーネントに切り出すかどうかは、画面の数ではなくマークアップの重複回数で決まる。今回は10箇所で同じ ",[18,751,31],{}," を書いていたので、1箇所直すたびに10箇所が同期する世界に変わった。次にcockpit-02やcase100-topic-2が増えても、",[18,754,755],{},"\u003CLessonBreadcrumb :items=\"...\" />"," を1行書けば終わる。",[14,758,759],{},"スクリプトで一括変換する判断は、ファイル数が3桁を超えた瞬間に自動的に発火させていい。手で当てる方が早いと感じる脳の錯覚は、30ファイルあたりで逆転する。",[761,762,763],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .s5V_Q, html code.shiki .s5V_Q{--shiki-default:#999999;--shiki-default-font-style:italic;--shiki-dark:#999999;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":130,"searchDepth":144,"depth":144,"links":765},[766,767,774,775,776],{"id":12,"depth":144,"text":12},{"id":35,"depth":144,"text":35,"children":768},[769,770,772,773],{"id":39,"depth":158,"text":40},{"id":54,"depth":158,"text":771},"2. 共通コンポーネント LessonBreadcrumb.vue を作成",{"id":449,"depth":158,"text":450},{"id":668,"depth":158,"text":669},{"id":695,"depth":144,"text":695},{"id":729,"depth":144,"text":729},{"id":746,"depth":144,"text":746},"dev","eurekapu-nuxt4のレッスン系ページでバラついていたパンクズを共通コンポーネントに集約し、case100ノート130ファイルをスクリプトで一括変換した記録","md",{},true,null,"/breadcrumb-unification","eurekapu-nuxt4",false,"2026-05-09T00:00:00.000Z",{"title":5,"description":778},"2026-05/2026-05-09/breadcrumb-unification",[790,791,792],"Vue","リファクタリング","UX改善","WjZhckmAFbCsdHLEHoRBfLxGJUhrPbMIRHgnVF2zydI",[],"https://log.eurekapu.com/og/blog/breadcrumb-unification.png?v=2026-05-09T00%3A00%3A00.000Z&title=%E5%85%A8%E3%83%AC%E3%83%83%E3%82%B9%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AEbreadcrumb%E3%82%92%E5%85%B1%E9%80%9A%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E5%8C%96%E3%81%97%E3%81%A6130%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%B8%80%E6%8B%AC%E5%A4%89%E6%8F%9B&author=Kei%20Komatsu&sig=36db9fb0d50dba4b",1782528834340]