Web Components と Declarative Shadow DOM の現在地:HTML だけで描くサーバー発 UI
近年、Web フロントエンド開発では JavaScript フレームワークが主流となっていますが、サーバーサイドレンダリング(SSR)の重要性が再認識される中で、新たな選択肢が注目を集めています。それが Declarative Shadow DOM です。
Web Components は長年「JavaScript が必要」という課題を抱えていましたが、Declarative Shadow DOM の登場により、サーバー側で完結する UI コンポーネントの実装が可能になりました。この技術は、ハイドレーションの複雑さを回避しながら、カプセル化されたスタイルと構造を持つコンポーネントを HTML だけで表現できるという革新的なアプローチをもたらします。
本記事では、Web Components の基礎から Declarative Shadow DOM の仕組み、そして実際のサーバー発 UI の実装方法まで、初心者の方にもわかりやすく解説していきますね。
背景
Web Components とは何か
Web Components は、再利用可能なカスタム要素を作成するための標準技術群です。主に以下の 3 つの技術で構成されています。
mermaidflowchart TB
wc["Web Components"] --> ce["Custom Elements<br/>独自タグの定義"]
wc --> sd["Shadow DOM<br/>スタイルのカプセル化"]
wc --> ht["HTML Templates<br/>再利用可能なマークアップ"]
ce --> ex1["例: <my-button>"]
sd --> ex2["スコープ付き CSS"]
ht --> ex3["<template> タグ"]
この図が示すように、Web Components は標準化された 3 つの要素技術を組み合わせることで、フレームワークに依存しない再利用可能なコンポーネントを実現します。
従来の Web Components の課題
従来の Web Components(Imperative Shadow DOM)には、いくつかの制約がありました。
mermaidflowchart LR
server["サーバー"] -->|HTML 送信| browser["ブラウザ"]
browser -->|JS 読み込み| js["JavaScript"]
js -->|attachShadow 呼び出し| shadow["Shadow DOM 構築"]
shadow -->|レンダリング| ui["UI 表示"]
style js fill:#ffcccc
style shadow fill:#ffcccc
上記のフローで示されるように、従来の方式では JavaScript の実行が必須でした。
従来方式の主な課題
| # | 課題 | 説明 |
|---|---|---|
| 1 | JavaScript 必須 | Shadow DOM の構築には必ず JavaScript が必要 |
| 2 | レンダリング遅延 | JS のダウンロード・パース・実行まで UI が表示されない |
| 3 | SSR 非対応 | サーバー側でレンダリングした内容が Shadow DOM に反映されない |
| 4 | ハイドレーション複雑化 | クライアント側での再構築が必要で実装が複雑 |
これらの課題により、パフォーマンスを重視するサイトや、JavaScript が無効な環境では、Web Components の採用が困難でした。
サーバーサイドレンダリングの重要性
現代の Web 開発では、初期表示速度や SEO の観点から SSR が重要視されています。
SSR が重要視される理由
- 初期表示速度の向上: ユーザーは JavaScript の実行を待たずにコンテンツを閲覧できます
- SEO 最適化: 検索エンジンのクローラーが完全な HTML を取得可能です
- アクセシビリティ: JavaScript が無効な環境でもコンテンツが利用可能になります
- Core Web Vitals 改善: LCP(Largest Contentful Paint)などの指標が向上します
しかし、従来の Web Components は SSR との相性が悪く、この問題を解決する必要がありました。
課題
Web Components と SSR の不整合
従来の Web Components を SSR で利用する場合、以下のような問題が発生していました。
mermaidsequenceDiagram
participant Server as サーバー
participant Browser as ブラウザ
participant JS as JavaScript
participant DOM as Shadow DOM
Server->>Browser: HTML 送信
Note over Browser: Light DOM のみ表示<br/>(不完全な UI)
Browser->>JS: スクリプト読み込み
JS->>DOM: attachShadow() 実行
DOM->>Browser: Shadow DOM 構築
Note over Browser: 完全な UI 表示<br/>(フラッシュ発生)
このシーケンス図から、サーバーから送信された HTML と最終的な UI の間にギャップがあることがわかります。
具体的な問題点
- 二重レンダリング: サーバー側でレンダリングした内容が、クライアント側で再構築される
- FOUC(Flash of Unstyled Content): スタイルが適用される前の状態が一瞬表示される
- パフォーマンス低下: JavaScript の実行を待つ必要があり、Time to Interactive が遅延する
- 重複コード: サーバー側とクライアント側で同じロジックを実装する必要がある
ハイドレーションの複雑さ
React や Vue などのフレームワークでは、SSR で生成された HTML に対してクライアント側でイベントリスナーなどを付与する「ハイドレーション」という処理が行われます。
typescript// 従来の Imperative Shadow DOM の例
class MyComponent extends HTMLElement {
connectedCallback() {
// JavaScript で Shadow DOM を構築
const shadowRoot = this.attachShadow({ mode: 'open' });
// サーバー側でレンダリングされた内容は使えない
shadowRoot.innerHTML = `
<style>
button { background: blue; color: white; }
</style>
<button>クリック</button>
`;
}
}
上記のコードでは、connectedCallback が実行されるまで Shadow DOM が構築されません。
この処理には以下の課題がありました。
| # | 課題 | 影響 |
|---|---|---|
| 1 | 状態の同期 | サーバー側の状態とクライアント側の状態を一致させる必要がある |
| 2 | タイミング制御 | ハイドレーション中にユーザー操作が発生すると不整合が起きる |
| 3 | バンドルサイズ増加 | ハイドレーション用のコードが必要になる |
| 4 | デバッグの困難さ | サーバーとクライアントの両方を考慮する必要がある |
パフォーマンスへの影響
JavaScript 必須の Web Components は、特にモバイル環境やネットワークが不安定な環境で、パフォーマンス上の問題を引き起こしていました。
パフォーマンスボトルネック
- JavaScript ダウンロード時間: ネットワーク速度に依存します
- パース・コンパイル時間: デバイスの CPU 性能に依存します
- 実行時間: DOM 操作は特にコストが高い処理です
- メモリ使用量: Shadow DOM の構築にはメモリが必要となります
これらの課題を解決するために登場したのが、Declarative Shadow DOM なのです。
解決策
Declarative Shadow DOM の登場
Declarative Shadow DOM は、HTML のみで Shadow DOM を宣言的に定義できる新しい仕様です。2020 年に Chrome 90 で初めて実装され、現在では主要ブラウザでサポートが進んでいます。
mermaidflowchart LR
server["サーバー"] -->|DSD 付き HTML| browser["ブラウザ"]
browser -->|パース| shadow["Shadow DOM 自動構築"]
shadow -->|即座にレンダリング| ui["UI 表示"]
style shadow fill:#ccffcc
style ui fill:#ccffcc
この図が示すように、JavaScript を必要とせず、ブラウザが HTML をパースする段階で Shadow DOM が自動的に構築されます。
Declarative Shadow DOM の特徴
| # | 特徴 | メリット |
|---|---|---|
| 1 | HTML のみで定義 | JavaScript 不要でサーバー側で完結 |
| 2 | 即座にレンダリング | JS の実行を待たずに表示される |
| 3 | SSR ネイティブ対応 | サーバー側でレンダリングした内容がそのまま使える |
| 4 | プログレッシブ・エンハンスメント | JS が無効でも基本機能が動作する |
基本的な構文
Declarative Shadow DOM は、<template> タグに shadowrootmode 属性を付与することで定義します。
html<!-- Declarative Shadow DOM の基本構文 -->
<my-component>
<template shadowrootmode="open">
<style>
button {
background: #0070f3;
color: white;
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
}
</style>
<button>
<slot></slot>
</button>
</template>
クリックしてください
</my-component>
このコードは、サーバー側で生成され、ブラウザに送信されると自動的に Shadow DOM として解釈されます。
shadowrootmode 属性の値
open: JavaScript からelement.shadowRootでアクセス可能closed: JavaScript からのアクセスを制限(推奨されない場合が多い)
スタイルのカプセル化
Declarative Shadow DOM の最大の利点の一つが、スタイルのカプセル化です。
html<!-- スタイルのカプセル化の例 -->
<style>
/* グローバルスタイル */
button {
background: red; /* これは影響しない */
}
</style>
<my-card>
<template shadowrootmode="open">
<style>
/* Shadow DOM 内のスタイル */
.card {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
button {
background: #0070f3; /* こちらが適用される */
color: white;
}
</style>
<div class="card">
<slot name="title"></slot>
<slot></slot>
<button>詳細を見る</button>
</div>
</template>
<h2 slot="title">カードタイトル</h2>
<p>カードの内容がここに入ります。</p>
</my-card>
上記のコードでは、グローバルの button スタイルは Shadow DOM 内の button に影響せず、Shadow DOM 内で定義されたスタイルのみが適用されます。
カプセル化のメリット
- CSS の命名衝突を回避できます
- コンポーネント単位でスタイルを管理できます
- グローバル CSS の影響を受けません
- リファクタリングが安全に行えます
スロットによるコンテンツ配置
<slot> 要素を使用することで、コンポーネントの利用者が提供するコンテンツを柔軟に配置できます。
html<!-- 名前付きスロットの活用 -->
<user-profile>
<template shadowrootmode="open">
<style>
.profile {
display: grid;
grid-template-columns: 80px 1fr;
gap: 16px;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
}
.avatar {
grid-row: 1 / 3;
}
.name {
font-size: 24px;
font-weight: bold;
margin: 0;
}
.bio {
color: #666;
margin: 0;
}
</style>
<div class="profile">
<div class="avatar">
<slot name="avatar"></slot>
</div>
<h2 class="name">
<slot name="name">名前未設定</slot>
</h2>
<p class="bio">
<slot name="bio">自己紹介文がありません</slot>
</p>
</div>
</template>
<img
slot="avatar"
src="/user.jpg"
alt="ユーザー画像"
width="80"
height="80"
/>
<span slot="name">山田太郎</span>
<span slot="bio"
>Web 開発者です。TypeScript と Next.js
が好きです。</span
>
</user-profile>
このコードでは、slot 属性を使って各コンテンツを適切な位置に配置しています。
スロットの種類と使い分け
| # | 種類 | 用途 | 記法 |
|---|---|---|---|
| 1 | デフォルトスロット | 名前なしのコンテンツを受け取る | <slot></slot> |
| 2 | 名前付きスロット | 特定の位置にコンテンツを配置 | <slot name="xxx"></slot> |
| 3 | フォールバックコンテンツ | スロットが空の場合のデフォルト | <slot>デフォルト</slot> |
サーバーサイドでの生成
Declarative Shadow DOM は、サーバーサイドで HTML を生成する際に最も力を発揮します。
Node.js での生成例
typescript// サーバーサイドでのコンポーネント生成関数
function generateButton(
text: string,
variant: 'primary' | 'secondary' = 'primary'
) {
const colors = {
primary: { bg: '#0070f3', color: 'white' },
secondary: { bg: '#f5f5f5', color: '#333' },
};
const style = colors[variant];
return `
<custom-button>
<template shadowrootmode="open">
<style>
button {
background: ${style.bg};
color: ${style.color};
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: opacity 0.2s;
}
button:hover {
opacity: 0.8;
}
</style>
<button>
<slot></slot>
</button>
</template>
${text}
</custom-button>
`;
}
この関数をサーバー側で呼び出すことで、動的にコンポーネントを生成できます。
typescript// Express での利用例
import express from 'express';
const app = express();
app.get('/', (req, res) => {
const html = `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Declarative Shadow DOM サンプル</title>
</head>
<body>
<h1>ボタンサンプル</h1>
${generateButton('送信する', 'primary')}
${generateButton('キャンセル', 'secondary')}
</body>
</html>
`;
res.send(html);
});
このコードでは、サーバー側で完全な HTML を生成し、クライアントに送信しています。JavaScript は一切必要ありません。
具体例
Next.js での実装
Next.js を使用して、Declarative Shadow DOM を活用したサーバーコンポーネントを実装してみましょう。
プロジェクトのセットアップ
bash# Next.js プロジェクトの作成
yarn create next-app my-dsd-app --typescript
cd my-dsd-app
# 必要なパッケージのインストール
yarn add react react-dom next
コンポーネントの作成
typescript// components/Card.tsx
// カードコンポーネントの型定義
interface CardProps {
title: string;
description: string;
imageUrl?: string;
href?: string;
}
型定義を行った後、コンポーネントのロジックを実装します。
typescript// components/Card.tsx(続き)
// Declarative Shadow DOM を使用したカードコンポーネント
export function Card({
title,
description,
imageUrl,
href,
}: CardProps) {
return (
<web-card>
<template shadowrootmode='open'>
<style>{`
.card {
display: flex;
flex-direction: column;
border: 1px solid #e0e0e0;
border-radius: 12px;
overflow: hidden;
transition: box-shadow 0.3s ease;
background: white;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-content {
padding: 20px;
}
.card-title {
font-size: 20px;
font-weight: bold;
margin: 0 0 12px 0;
color: #333;
}
.card-description {
font-size: 14px;
color: #666;
line-height: 1.6;
margin: 0;
}
.card-link {
display: block;
text-decoration: none;
color: inherit;
}
`}</style>
<a className='card-link' href={href || '#'}>
<div className='card'>
{imageUrl && (
<img
className='card-image'
src={imageUrl}
alt={title}
loading='lazy'
/>
)}
<div className='card-content'>
<h3 className='card-title'>{title}</h3>
<p className='card-description'>
{description}
</p>
</div>
</div>
</a>
</template>
</web-card>
);
}
このコンポーネントは、サーバー側でレンダリングされ、完全なスタイル付きの HTML としてクライアントに配信されます。
データフェッチとの組み合わせ
Next.js の Server Components を活用して、データフェッチと組み合わせた実装を見てみましょう。
typescript// app/page.tsx
// サーバーコンポーネントでのデータフェッチ
interface Article {
id: number;
title: string;
excerpt: string;
thumbnail: string;
slug: string;
}
データの型を定義した後、フェッチ関数を実装します。
typescript// app/page.tsx(続き)
// 記事データの取得関数
async function getArticles(): Promise<Article[]> {
// 実際の API エンドポイントからデータを取得
const res = await fetch(
'https://api.example.com/articles',
{
next: { revalidate: 3600 }, // 1時間キャッシュ
}
);
if (!res.ok) {
throw new Error('記事の取得に失敗しました');
}
return res.json();
}
取得したデータをコンポーネントで使用します。
typescript// app/page.tsx(続き)
import { Card } from '@/components/Card';
// メインページコンポーネント
export default async function Home() {
// サーバー側でデータフェッチ
const articles = await getArticles();
return (
<main style={{ padding: '40px' }}>
<h1>最新記事</h1>
<div
style={{
display: 'grid',
gridTemplateColumns:
'repeat(auto-fill, minmax(300px, 1fr))',
gap: '24px',
marginTop: '24px',
}}
>
{articles.map((article) => (
<Card
key={article.id}
title={article.title}
description={article.excerpt}
imageUrl={article.thumbnail}
href={`/articles/${article.slug}`}
/>
))}
</div>
</main>
);
}
このコードにより、サーバー側で記事データを取得し、Declarative Shadow DOM を使用したカードコンポーネントとしてレンダリングされます。JavaScript の実行を待たずに、完全なスタイル付きのコンテンツが表示されます。
このアプローチのメリット
mermaidflowchart TB
req["ユーザーリクエスト"] --> server["Next.js サーバー"]
server --> fetch["API データフェッチ"]
fetch --> render["HTML 生成<br/>(DSD 含む)"]
render --> send["完全な HTML 送信"]
send --> browser["ブラウザ"]
browser --> instant["即座に表示"]
style instant fill:#ccffcc
上の図が示すように、完全にレンダリングされた HTML がクライアントに送信されるため、ユーザーは即座にコンテンツを閲覧できます。
インタラクティブ性の追加
Declarative Shadow DOM で構築した UI に、必要に応じて JavaScript でインタラクティブ性を追加することもできます。これをプログレッシブ・エンハンスメントと呼びます。
typescript// components/InteractiveCard.tsx
'use client'; // クライアントコンポーネントとして宣言
import { useEffect, useRef } from 'react';
interface InteractiveCardProps {
title: string;
description: string;
onCardClick?: () => void;
}
型定義の後、インタラクティブなコンポーネントを実装します。
typescript// components/InteractiveCard.tsx(続き)
export function InteractiveCard({
title,
description,
onCardClick,
}: InteractiveCardProps) {
const cardRef = useRef<HTMLElement>(null);
// クライアント側でのイベントリスナー追加
useEffect(() => {
const card = cardRef.current;
if (!card) return;
const handleClick = () => {
console.log(`カードがクリックされました: ${title}`);
onCardClick?.();
};
// Shadow Root 内の要素にアクセス
const shadowRoot = card.shadowRoot;
if (shadowRoot) {
const cardElement = shadowRoot.querySelector('.card');
cardElement?.addEventListener('click', handleClick);
// クリーンアップ関数
return () => {
cardElement?.removeEventListener(
'click',
handleClick
);
};
}
}, [title, onCardClick]);
return (
<interactive-card ref={cardRef}>
<template shadowrootmode='open'>
<style>{`
.card {
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
.card:active {
transform: translateY(-2px);
}
`}</style>
<div className='card'>
<h3>{title}</h3>
<p>{description}</p>
</div>
</template>
</interactive-card>
);
}
このアプローチにより、基本的な UI は JavaScript なしで表示され、JavaScript が利用可能な環境では追加のインタラクティブ性が提供されます。
Express での実装例
Node.js と Express を使用したシンプルなサーバー実装も見てみましょう。
typescript// server.ts
// 必要なモジュールのインポート
import express from 'express';
import type { Request, Response } from 'express';
基本的な設定とヘルパー関数を実装します。
typescript// server.ts(続き)
// Express アプリケーションの初期化
const app = express();
const PORT = 3000;
// DSD コンポーネント生成ヘルパー
function createAlert(
message: string,
type: 'info' | 'warning' | 'error'
) {
const colors = {
info: {
bg: '#e3f2fd',
border: '#2196f3',
text: '#0d47a1',
},
warning: {
bg: '#fff3e0',
border: '#ff9800',
text: '#e65100',
},
error: {
bg: '#ffebee',
border: '#f44336',
text: '#b71c1c',
},
};
const style = colors[type];
return `
<custom-alert>
<template shadowrootmode="open">
<style>
.alert {
background: ${style.bg};
border-left: 4px solid ${style.border};
color: ${style.text};
padding: 16px;
border-radius: 4px;
margin: 16px 0;
font-family: system-ui, -apple-system, sans-serif;
}
.alert-icon {
display: inline-block;
margin-right: 8px;
font-weight: bold;
}
</style>
<div class="alert">
<span class="alert-icon">ℹ️</span>
<slot>${message}</slot>
</div>
</template>
</custom-alert>
`;
}
ルートハンドラーを実装します。
typescript// server.ts(続き)
// ルートエンドポイント
app.get('/', (req: Request, res: Response) => {
const html = `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Declarative Shadow DOM サーバーサンプル</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
background: #f5f5f5;
}
h1 {
color: #333;
}
</style>
</head>
<body>
<h1>システム通知</h1>
${createAlert('正常に保存されました', 'info')}
${createAlert('この操作は取り消せません', 'warning')}
${createAlert('エラーが発生しました', 'error')}
</body>
</html>
`;
res.send(html);
});
// サーバーの起動
app.listen(PORT, () => {
console.log(
`サーバーが http://localhost:${PORT} で起動しました`
);
});
このサーバーは、完全にスタイル付けされたアラートコンポーネントを、JavaScript なしで配信します。
実行方法
bash# TypeScript のコンパイルと実行
yarn add express @types/express typescript ts-node
yarn ts-node server.ts
ブラウザで http://localhost:3000 にアクセスすると、JavaScript の実行を待たずに、完全にスタイル付けされたアラートが表示されます。
ブラウザサポートの確認
現在の Declarative Shadow DOM のブラウザサポート状況を確認しましょう。
主要ブラウザのサポート状況(2025 年 1 月時点)
| # | ブラウザ | バージョン | サポート状況 |
|---|---|---|---|
| 1 | Chrome | 90+ | ★★★ 完全サポート |
| 2 | Edge | 91+ | ★★★ 完全サポート |
| 3 | Safari | 16.4+ | ★★★ 完全サポート |
| 4 | Firefox | 123+ | ★★★ 完全サポート |
| 5 | Opera | 76+ | ★★★ 完全サポート |
主要ブラウザでのサポートが進んでおり、本番環境での利用も十分に可能な状況です。
フォールバック戦略
古いブラウザのサポートが必要な場合は、ポリフィルを使用できます。
typescript// polyfill.ts
// Declarative Shadow DOM のポリフィル
(function () {
// すでにサポートされている場合は何もしない
if (
document.createElement('template').shadowRootMode !==
undefined
) {
return;
}
// ページ読み込み時にポリフィルを実行
function polyfillDeclarativeShadowDOM() {
// shadowrootmode 属性を持つ template 要素を検索
document
.querySelectorAll('template[shadowrootmode]')
.forEach((template) => {
const mode = template.getAttribute(
'shadowrootmode'
) as 'open' | 'closed';
const parent = template.parentElement;
if (!parent) return;
// Shadow Root を作成
const shadowRoot = parent.attachShadow({ mode });
// template の内容を Shadow Root に移動
shadowRoot.appendChild(template.content);
// template 要素を削除
template.remove();
});
}
// DOM 読み込み完了時に実行
if (document.readyState === 'loading') {
document.addEventListener(
'DOMContentLoaded',
polyfillDeclarativeShadowDOM
);
} else {
polyfillDeclarativeShadowDOM();
}
})();
このポリフィルをページの <head> 内で読み込むことで、古いブラウザでも Declarative Shadow DOM が動作するようになります。
html<head>
<script src="/polyfill.js"></script>
</head>
ただし、ポリフィルを使用する場合は JavaScript が必須となるため、本来の「JavaScript 不要」というメリットは失われる点に注意が必要です。
まとめ
Declarative Shadow DOM は、Web Components の長年の課題であった「JavaScript 必須」という制約を解決し、サーバーサイドレンダリングとの完全な統合を実現した画期的な技術です。
本記事のポイント
- HTML だけでコンポーネント化: JavaScript なしでカプセル化された UI を実装できます
- SSR ネイティブ対応: サーバー側で生成した HTML がそのままブラウザで動作します
- パフォーマンス向上: JavaScript の実行を待たずに UI が表示されます
- プログレッシブ・エンハンスメント: 必要に応じて JavaScript で機能を追加できます
- ブラウザサポート拡大: 主要ブラウザで利用可能になっています
この技術は、特に以下のようなケースで威力を発揮するでしょう。
- コンテンツ中心の Web サイト(ブログ、ニュースサイトなど)
- SEO が重要なマーケティングサイト
- 初期表示速度を重視するサービス
- JavaScript のバンドルサイズを削減したい場合
- シンプルなコンポーネントライブラリの構築
React や Vue などのフレームワークが主流の現在ですが、Declarative Shadow DOM は「すべてを JavaScript で実装する」という従来のアプローチに対する重要な選択肢を提供しています。
プロジェクトの要件に応じて、フレームワークと Declarative Shadow DOM を組み合わせることで、より効率的で保守性の高い Web アプリケーションを構築できるようになりました。
今後、この技術がさらに普及し、Web 標準としてより多くの開発者に活用されることが期待されますね。
関連リンク
articleWeb Components と Declarative Shadow DOM の現在地:HTML だけで描くサーバー発 UI
articleWeb Components のパッケージ配布戦略:types/CEM(Custom Elements Manifest)/ドキュメント自動
articleWeb Components が “is not a constructor” で落ちる時:定義順序と複数登録の衝突を解決
articleWeb Components vs Lit:素の実装とフレームワーク補助の DX/サイズ/速度を実測比較
articleWeb Components で社内デザインシステム基盤を作る:複数フレームワーク横断のコア層
articleWeb Components で作るモーダルダイアログ:フォーカス管理・閉じる動線まで実装
articleYarn vs npm vs pnpm 徹底比較:速度・メモリ・ディスク・再現性を実測
articleWeb Components と Declarative Shadow DOM の現在地:HTML だけで描くサーバー発 UI
articleVue.js ルーター戦略比較:ネスト/動的セグメント/ガードの設計コスト
articleCursor × Monorepo 構築:Yarn Workspaces/Turborepo/tsconfig path のベストプラクティス
articleTailwind CSS のコンテナクエリ vs 伝統的ブレイクポイント:適応精度を実測
articleCline × Monorepo(Yarn Workspaces)導入:パス解決とルート権限の最適解
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来