Electron スクリーンレコーダー/キャプチャツールを desktopCapturer で作る

デスクトップアプリケーションで画面録画機能を実装したいと思ったことはありませんか。実は、Electron のdesktopCapturer
API を使えば、比較的シンプルなコードでスクリーンレコーダーやキャプチャツールを作ることができます。
この記事では、Electron のdesktopCapturer
を使って、実際に動作するスクリーンレコーダーを一から実装する方法を解説します。画面ソースの取得から録画の開始・停止、ファイル保存まで、実践的なコード例とともに詳しくご紹介しますね。
背景
デスクトップアプリでの画面キャプチャニーズ
近年、リモートワークやオンライン会議の普及により、画面共有や画面録画の需要が急増しています。オンライン教育、プレゼンテーション、チュートリアル動画の作成など、さまざまな場面で画面キャプチャ機能が必要とされるようになりました。
こうしたニーズに応えるため、多くの企業やエンジニアがデスクトップアプリケーションで画面録画機能を実装しようとしています。しかし、ネイティブ API を直接扱うのは難しく、プラットフォームごとの違いも大きな課題でした。
Electron の desktopCapturer とは
Electron は、Web 技術でデスクトップアプリを開発できるフレームワークです。その中でdesktopCapturer
API は、画面全体やアプリケーションウィンドウのメディアストリームを取得するための機能を提供します。
この API を使うことで、Chromium のメディア API と連携し、画面の映像をキャプチャできます。さらに、取得したストリームはMediaRecorder
API で録画したり、Canvas に描画して静止画として保存したりすることが可能です。
以下の図は、desktopCapturer を使った画面キャプチャの基本的な流れを示しています。
mermaidflowchart LR
user["ユーザー"] -->|録画開始| electron["Electron<br/>アプリ"]
electron -->|getSources| capturer["desktopCapturer"]
capturer -->|画面リスト| electron
electron -->|選択| stream["MediaStream"]
stream -->|録画| recorder["MediaRecorder"]
recorder -->|データ| file["ファイル保存"]
file -->|完了通知| user
図で理解できる要点:
- desktopCapturer が利用可能な画面ソースを列挙
- ユーザーが選択したソースから MediaStream を取得
- MediaRecorder で録画してファイルとして保存
課題
クロスプラットフォーム対応の難しさ
画面キャプチャ機能を実装する際の最大の課題は、Windows、macOS、Linux といった異なるプラットフォームで一貫した動作を実現することです。各 OS はそれぞれ独自の画面キャプチャ API を持っており、直接扱うには専門知識が必要になります。
また、セキュリティやプライバシーの観点から、各 OS は画面録画に対して異なる権限管理を行っています。macOS では画面収録の許可が必要ですし、Windows でも特定のアプリケーションウィンドウのキャプチャには制限があるケースがあります。
メディアストリームの取り扱い
画面から取得したメディアストリームは、適切に処理しないとメモリリークやパフォーマンス低下を引き起こします。特に長時間の録画では、データの蓄積やエンコーディング処理が負荷になることがあります。
また、録画したデータをどの形式で保存するか、どのようにユーザーに提供するか、といった設計判断も必要です。WebM、MP4 など、ブラウザや OS でサポートされる形式を選択する必要があります。
ユーザー体験の設計
画面録画ツールとして使いやすくするには、以下のような機能が求められます。
# | 機能 | 説明 |
---|---|---|
1 | ソース選択 UI | ユーザーがキャプチャしたい画面やウィンドウを視覚的に選べるインターフェース |
2 | 録画制御 | 開始・一時停止・停止などの直感的な操作 |
3 | プレビュー機能 | 録画前に選択した画面が正しいか確認できる機能 |
4 | ファイル管理 | 録画したファイルの保存場所指定や自動命名 |
以下の図は、ユーザーが画面録画ツールを使う際の典型的なフローを示しています。
mermaidflowchart TD
start["アプリ起動"] --> select["画面ソース<br/>選択画面"]
select --> preview["プレビュー<br/>確認"]
preview -->|OK| record["録画開始"]
preview -->|変更| select
record --> recording["録画中"]
recording --> stop["録画停止"]
stop --> save["ファイル保存"]
save --> done["完了"]
解決策
desktopCapturer API の活用
Electron のdesktopCapturer
を使うことで、クロスプラットフォームで動作する画面キャプチャ機能を実装できます。この API は、レンダラープロセスから利用可能で、画面やウィンドウのリストを取得し、それらをメディアストリームとして扱えます。
主な利点は以下の通りです。
# | 利点 | 詳細 |
---|---|---|
1 | クロスプラットフォーム | Windows、macOS、Linux で同じコードが動作 |
2 | Web 標準 API 連携 | MediaRecorder、Canvas など標準 API と組み合わせ可能 |
3 | サムネイル取得 | 各ソースのプレビュー画像を取得できる |
4 | 柔軟な選択 | 画面全体、特定ウィンドウ、特定のディスプレイを選択可能 |
実装アーキテクチャ
画面録画アプリケーションの実装は、以下のような構造で設計します。
アーキテクチャの概要図:
mermaidflowchart TB
subgraph renderer["レンダラープロセス"]
ui["UI<br/>コンポーネント"]
capturer["desktopCapturer"]
stream["MediaStream"]
recorder["MediaRecorder"]
end
subgraph main["メインプロセス"]
window["BrowserWindow"]
file["ファイル保存<br/>処理"]
end
ui -->|getSources| capturer
capturer -->|ソースリスト| ui
ui -->|getUserMedia| stream
stream -->|録画| recorder
recorder -->|Blob データ| ui
ui -->|IPC| main
main -->|書き込み| file
図で理解できる要点:
- レンダラープロセスでメディア処理を完結
- メインプロセスはファイル保存など特権操作のみ担当
- IPC で必要最小限の通信を行う
セキュリティ設定
Electron 10 以降、セキュリティ強化のためdesktopCapturer
の使用には明示的な有効化が必要です。BrowserWindow の作成時に、以下のようにwebPreferences
で許可します。
javascriptconst { app, BrowserWindow } = require('electron');
app.whenReady().then(() => {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
// desktopCapturerを有効化
desktopCapturer: true,
},
});
});
この設定により、レンダラープロセスからdesktopCapturer
API にアクセスできるようになります。contextIsolation
をtrue
にすることで、preload スクリプトを通じて安全に API を公開できますね。
具体例
プロジェクトのセットアップ
まず、Electron プロジェクトの基本構造を作成します。
package.json の設定:
json{
"name": "electron-screen-recorder",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"dependencies": {
"electron": "^28.0.0"
}
}
Yarn を使ってインストールします。
bashyarn install
メインプロセスの実装
メインプロセス(main.js)では、アプリケーションウィンドウを作成し、desktopCapturer を有効化します。
メインプロセスの初期化:
javascript// main.js
const {
app,
BrowserWindow,
ipcMain,
dialog,
} = require('electron');
const path = require('path');
const fs = require('fs');
let mainWindow;
// アプリケーション起動時の処理
app.whenReady().then(() => {
createMainWindow();
});
メインウィンドウを作成する関数を定義します。
BrowserWindow の作成:
javascriptfunction createMainWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
// desktopCapturerの使用を許可
desktopCapturer: true,
},
});
mainWindow.loadFile('index.html');
// 開発時はDevToolsを開く
mainWindow.webContents.openDevTools();
}
録画データの保存処理を IPC で受け取ります。
ファイル保存の IPC 処理:
javascript// 録画データを保存するIPCハンドラー
ipcMain.handle('save-recording', async (event, buffer) => {
try {
// 保存先ダイアログを表示
const { filePath } = await dialog.showSaveDialog({
buttonLabel: '保存',
defaultPath: `recording-${Date.now()}.webm`,
filters: [
{ name: 'WebM Video', extensions: ['webm'] },
],
});
if (filePath) {
// Bufferデータをファイルに書き込み
await fs.promises.writeFile(
filePath,
Buffer.from(buffer)
);
return { success: true, filePath };
}
return { success: false };
} catch (error) {
console.error('保存エラー:', error);
return { success: false, error: error.message };
}
});
Preload スクリプトの実装
Preload スクリプトは、レンダラープロセスとメインプロセスの橋渡しをします。contextIsolation が有効な場合、ここで API を安全に公開します。
Preload スクリプトの基本構造:
javascript// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// レンダラープロセスに安全にAPIを公開
contextBridge.exposeInMainWorld('electronAPI', {
// 画面ソースを取得
getSources: async () => {
const { desktopCapturer } = require('electron');
return await desktopCapturer.getSources({
types: ['window', 'screen'],
thumbnailSize: { width: 320, height: 180 },
});
},
// 録画データを保存
saveRecording: (buffer) => {
return ipcRenderer.invoke('save-recording', buffer);
},
});
このスクリプトにより、レンダラープロセスからwindow.electronAPI
経由で安全に Electron API を呼び出せます。
レンダラープロセス(HTML)の実装
ユーザーインターフェースを作成します。画面ソース選択、プレビュー、録画制御のボタンを配置します。
HTML の基本構造:
html<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>画面録画ツール</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="container">
<h1>画面録画ツール</h1>
<!-- 録画制御ボタン -->
<div class="controls">
<button
id="selectSourceBtn"
class="btn btn-primary"
>
画面を選択
</button>
<button
id="startBtn"
class="btn btn-success"
disabled
>
録画開始
</button>
<button
id="stopBtn"
class="btn btn-danger"
disabled
>
録画停止
</button>
</div>
</div>
</body>
</html>
プレビュー表示用のビデオ要素を追加します。
ビデオプレビューエリア:
html <!-- プレビューエリア -->
<div class="preview-area">
<video id="preview" autoplay muted></video>
<div id="recordingIndicator" class="recording-indicator hidden">
● REC
</div>
</div>
<!-- ソース選択モーダル -->
<div id="sourceModal" class="modal hidden">
<div class="modal-content">
<h2>キャプチャする画面を選択</h2>
<div id="sourceList" class="source-list"></div>
<button id="closeModal" class="btn">キャンセル</button>
</div>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>
レンダラープロセス(JavaScript)の実装
画面録画の核となる JavaScript ロジックを実装します。
初期変数と DOM 要素の取得:
javascript// renderer.js
let mediaRecorder;
let recordedChunks = [];
let selectedSourceId = null;
let currentStream = null;
// DOM要素の取得
const selectSourceBtn = document.getElementById(
'selectSourceBtn'
);
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const preview = document.getElementById('preview');
const sourceModal = document.getElementById('sourceModal');
const sourceList = document.getElementById('sourceList');
const closeModal = document.getElementById('closeModal');
const recordingIndicator = document.getElementById(
'recordingIndicator'
);
画面ソース選択機能を実装します。
画面ソース選択処理:
javascript// 画面ソース選択ボタンのクリック処理
selectSourceBtn.addEventListener('click', async () => {
try {
// 利用可能な画面ソースを取得
const sources = await window.electronAPI.getSources();
// ソースリストをクリア
sourceList.innerHTML = '';
// 各ソースをカード形式で表示
sources.forEach((source) => {
const sourceCard = document.createElement('div');
sourceCard.className = 'source-card';
sourceCard.innerHTML = `
<img src="${source.thumbnail.toDataURL()}" alt="${
source.name
}">
<p>${source.name}</p>
`;
// クリックで選択
sourceCard.addEventListener('click', () => {
selectSource(source.id);
sourceModal.classList.add('hidden');
});
sourceList.appendChild(sourceCard);
});
// モーダルを表示
sourceModal.classList.remove('hidden');
} catch (error) {
console.error('ソース取得エラー:', error);
alert('画面ソースの取得に失敗しました');
}
});
選択したソースから MediaStream を取得し、プレビューを表示します。
MediaStream の取得とプレビュー:
javascript// 画面ソースを選択してストリームを取得
async function selectSource(sourceId) {
selectedSourceId = sourceId;
try {
// 既存のストリームがあれば停止
if (currentStream) {
currentStream
.getTracks()
.forEach((track) => track.stop());
}
// MediaStreamを取得
currentStream =
await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId,
},
},
});
// プレビューに表示
preview.srcObject = currentStream;
preview.play();
// 録画開始ボタンを有効化
startBtn.disabled = false;
console.log('画面ソースを選択しました:', sourceId);
} catch (error) {
console.error('MediaStream取得エラー:', error);
alert('画面キャプチャの開始に失敗しました');
}
}
録画開始処理を実装します。MediaRecorder を使って録画を行います。
録画開始処理:
javascript// 録画開始ボタンのクリック処理
startBtn.addEventListener('click', () => {
if (!currentStream) {
alert('先に画面を選択してください');
return;
}
// 録画データをリセット
recordedChunks = [];
// MediaRecorderを作成
mediaRecorder = new MediaRecorder(currentStream, {
mimeType: 'video/webm; codecs=vp9',
videoBitsPerSecond: 3000000, // 3Mbps
});
// データが利用可能になったら保存
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
// 録画停止時の処理
mediaRecorder.onstop = handleRecordingStop;
// 録画開始
mediaRecorder.start();
// UIの更新
startBtn.disabled = true;
stopBtn.disabled = false;
selectSourceBtn.disabled = true;
recordingIndicator.classList.remove('hidden');
console.log('録画を開始しました');
});
録画停止処理を実装します。
録画停止処理:
javascript// 録画停止ボタンのクリック処理
stopBtn.addEventListener('click', () => {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
// UIの更新
startBtn.disabled = false;
stopBtn.disabled = true;
selectSourceBtn.disabled = false;
recordingIndicator.classList.add('hidden');
console.log('録画を停止しました');
}
});
録画データをファイルとして保存する処理を実装します。
録画データの保存処理:
javascript// 録画停止時の処理
async function handleRecordingStop() {
// Blobデータを作成
const blob = new Blob(recordedChunks, {
type: 'video/webm',
});
// BlobをArrayBufferに変換
const buffer = await blob.arrayBuffer();
try {
// メインプロセスに保存を依頼
const result = await window.electronAPI.saveRecording(
buffer
);
if (result.success) {
alert(`録画を保存しました:\n${result.filePath}`);
} else {
alert('録画の保存がキャンセルされました');
}
} catch (error) {
console.error('保存エラー:', error);
alert('録画の保存に失敗しました');
}
}
モーダルのクローズ処理を追加します。
モーダルクローズ処理:
javascript// モーダルを閉じる
closeModal.addEventListener('click', () => {
sourceModal.classList.add('hidden');
});
// ウィンドウ終了時にストリームをクリーンアップ
window.addEventListener('beforeunload', () => {
if (currentStream) {
currentStream
.getTracks()
.forEach((track) => track.stop());
}
});
スタイルシートの実装
見栄えの良い UI を作成するため、CSS を追加します。
基本スタイル:
css/* styles.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(
135deg,
#667eea 0%,
#764ba2 100%
);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 1000px;
width: 100%;
}
ボタンとコントロールのスタイルを定義します。
ボタンスタイル:
cssh1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 28px;
}
.controls {
display: flex;
gap: 15px;
justify-content: center;
margin-bottom: 30px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-success {
background: #10b981;
color: white;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn:not(:disabled):hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
プレビューエリアのスタイルを追加します。
プレビューエリアスタイル:
css.preview-area {
position: relative;
background: #000;
border-radius: 8px;
overflow: hidden;
aspect-ratio: 16 / 9;
}
#preview {
width: 100%;
height: 100%;
object-fit: contain;
}
.recording-indicator {
position: absolute;
top: 20px;
right: 20px;
background: #ef4444;
color: white;
padding: 8px 16px;
border-radius: 20px;
font-weight: bold;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
.hidden {
display: none;
}
ソース選択モーダルのスタイルを定義します。
モーダルスタイル:
css.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 12px;
max-width: 800px;
max-height: 80vh;
overflow-y: auto;
width: 90%;
}
.modal-content h2 {
margin-bottom: 20px;
color: #333;
}
.source-list {
display: grid;
grid-template-columns: repeat(
auto-fill,
minmax(250px, 1fr)
);
gap: 20px;
margin-bottom: 20px;
}
.source-card {
border: 2px solid #e5e7eb;
border-radius: 8px;
padding: 15px;
cursor: pointer;
transition: all 0.3s ease;
}
.source-card:hover {
border-color: #667eea;
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.source-card img {
width: 100%;
border-radius: 4px;
margin-bottom: 10px;
}
.source-card p {
text-align: center;
color: #666;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
アプリケーションの実行
すべてのファイルが準備できたら、アプリケーションを起動します。
bashyarn start
これで画面録画ツールが起動し、以下の操作が可能になります。
# | 操作 | 説明 |
---|---|---|
1 | 画面選択 | 利用可能な画面やウィンドウのサムネイルから選択 |
2 | プレビュー確認 | 選択した画面がリアルタイムで表示される |
3 | 録画開始 | ワンクリックで録画スタート |
4 | 録画停止 | 録画を終了し、保存ダイアログを表示 |
エラーハンドリングの強化
本番環境では、より詳細なエラーハンドリングを追加することをお勧めします。
権限エラーの処理:
javascript// MediaStream取得時のエラー処理を強化
async function selectSource(sourceId) {
selectedSourceId = sourceId;
try {
if (currentStream) {
currentStream
.getTracks()
.forEach((track) => track.stop());
}
currentStream =
await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId,
},
},
});
preview.srcObject = currentStream;
preview.play();
startBtn.disabled = false;
} catch (error) {
// エラーの種類に応じて適切なメッセージを表示
let errorMessage = '画面キャプチャの開始に失敗しました';
if (error.name === 'NotAllowedError') {
errorMessage =
'画面録画の権限が拒否されました。\n' +
'システム設定で権限を許可してください。';
} else if (error.name === 'NotFoundError') {
errorMessage =
'指定された画面ソースが見つかりませんでした。';
} else if (error.name === 'NotReadableError') {
errorMessage =
'画面ソースにアクセスできません。\n' +
'他のアプリケーションが使用中の可能性があります。';
}
console.error('MediaStream取得エラー:', error);
alert(errorMessage);
}
}
音声キャプチャの追加
画面だけでなく、システム音声も一緒に録音したい場合は、以下のように変更します。
音声付き録画の実装:
javascriptasync function selectSource(sourceId) {
selectedSourceId = sourceId;
try {
if (currentStream) {
currentStream
.getTracks()
.forEach((track) => track.stop());
}
// ビデオストリームを取得
const videoStream =
await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId,
},
},
});
// オーディオストリームを取得
const audioStream =
await navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
chromeMediaSource: 'desktop',
},
},
video: false,
});
// ビデオとオーディオを結合
currentStream = new MediaStream([
...videoStream.getVideoTracks(),
...audioStream.getAudioTracks(),
]);
preview.srcObject = currentStream;
preview.play();
startBtn.disabled = false;
} catch (error) {
console.error('ストリーム取得エラー:', error);
alert('画面・音声キャプチャの開始に失敗しました');
}
}
音声キャプチャを含む場合、macOS ではマイクの権限も必要になることに注意が必要です。
プロジェクト構造の全体像
最終的なプロジェクトのファイル構造は以下のようになります。
textelectron-screen-recorder/
├── package.json
├── main.js # メインプロセス
├── preload.js # プリロードスクリプト
├── index.html # UI
├── renderer.js # レンダラープロセスのロジック
└── styles.css # スタイルシート
この構成により、関心の分離が明確になり、保守性の高いコードベースが実現できます。
まとめ
Electron のdesktopCapturer
API を使うことで、クロスプラットフォームで動作するスクリーンレコーダーを比較的簡単に実装できます。この記事では、画面ソースの取得から MediaStream の処理、MediaRecorder による録画、そしてファイル保存まで、実践的な実装方法を解説しました。
重要なポイントをまとめます。
# | ポイント | 詳細 |
---|---|---|
1 | desktopCapturer の有効化 | BrowserWindow の webPreferences で明示的に許可 |
2 | contextIsolation との連携 | preload スクリプトで安全に API を公開 |
3 | MediaRecorder の活用 | Web 標準 API で録画処理を実装 |
4 | エラーハンドリング | 権限エラーやソース取得失敗に対応 |
5 | ストリームの適切な管理 | メモリリークを防ぐためのクリーンアップ |
この基本実装をベースに、以下のような機能拡張も可能です。
- カウントダウンタイマーの追加
- 録画中の一時停止・再開機能
- ウェブカメラの映像をピクチャーインピクチャーで重ねる
- 録画中の描画やアノテーション機能
- カスタム録画設定(解像度、フレームレート、ビットレート)
- クラウドストレージへの自動アップロード
Electron の強力な API 群を活用することで、Web の知識だけで本格的なデスクトップアプリケーションを構築できることがお分かりいただけたのではないでしょうか。ぜひ、この実装をベースに、独自の画面録画ツールを開発してみてください。
関連リンク
- article
Electron スクリーンレコーダー/キャプチャツールを desktopCapturer で作る
- article
Electron クリーンアーキテクチャ設計:ドメインと UI を IPC で疎結合に
- article
Electron セキュリティ設定チートシート:webPreferences/CSP/許可リスト早見表
- article
Electron セットアップ最短ルート:Vite + TypeScript + ESLint + Preload 分離
- article
Electron vs Tauri vs Flutter Desktop:サイズ/速度/互換を実測比較
- article
Electron トラブルシュート:白画面(White Screen)問題を 3 分で切り分け
- article
shadcn/ui で Command Palette を実装:検索・履歴・キーボードショートカット対応
- article
GPT-5 本番運用の SLO 設計:品質(正確性/再現性)・遅延・コストの三点均衡を保つ
- article
Emotion の「変種(variants)」設計パターン:props→ スタイルの型安全マッピング
- article
Remix でブログをゼロから構築:Markdown・検索・タグ・OGP まで実装
- article
Preact でミニブログを 1 日で公開:ルーティング・MDX・SEO まで一気通貫
- article
Electron スクリーンレコーダー/キャプチャツールを desktopCapturer で作る
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来