Angularのハイドレーションとは、サーバー(SSR)で作ったHTMLを捨てずに再利用し、ブラウザ側でイベントや状態を「つなぎ直す」仕組みです。incremental hydration(インクリメンタルハイドレーション)は、それを @defer ブロック単位で「必要になったときだけ」行う機能です。

この記事では、Angular 22.2.0 で実際にSSRアプリを作成・ビルドし、Playwright(Chromium 141)で 「どのJSファイルが、いつ読み込まれたか」を実測 しました。あわせて、ハイドレーション不一致(NG0500)をわざと起こし、実際に出たコンソールエラー文と、直す前・直した後の結果 を載せています。v22で withIncrementalHydration() が非推奨になり既定で有効になった点など、最新版での書き方の違いもまとめました。


Angular SSR・ハイドレーションの早見表

やりたいこと v22の書き方 補足
SSR付きで新規作成 ng new my-app --ssr provideClientHydration() が最初から入る
ハイドレーションを有効化 provideClientHydration() サーバー側設定にも含まれる必要あり(mergeApplicationConfig で共有)
incremental hydration を有効化 何もしなくてよい(v22から既定で有効) withIncrementalHydration() は v22.0.0 で非推奨
incremental hydration を無効化 provideClientHydration(withNoIncrementalHydration()) v22で追加された逆方向のスイッチ
ハイドレーション前のクリックを再生 withEventReplay() incremental hydration有効時は自動で有効
見えたらハイドレーション @defer (hydrate on viewport) { … } IntersectionObserverで判定
操作されたらハイドレーション @defer (hydrate on interaction) { … } click / keydown
ホバーでハイドレーション @defer (hydrate on hover) { … } mouseover / focusin
手が空いたらハイドレーション @defer (hydrate on idle) { … } requestIdleCallback相当
一定時間後 @defer (hydrate on timer(2s)) { … } ms / s 指定
条件で @defer (hydrate when isReady()) { … } 最上位の未ハイドレーションブロックのときだけ評価
静的なまま(JSを読まない) @defer (hydrate never) { … } 配下のネストもすべて対象外
特定コンポーネントだけ対象外 <app-x ngSkipHydration /> ホスト要素に付ける。ルートに付けると全体が無効
DOM操作をブラウザだけで行う afterNextRender(() => { … }) サーバーでは実行されない
API結果の二重取得を防ぐ provideHttpClient(withFetch()) + 既定の転送キャッシュ 無効化は withNoHttpTransferCache()
i18nブロックもハイドレーション withI18nSupport() 既定では i18n ブロックは再描画

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angular SSRハイドレーション|incremental hydration実測」


SSR → ハイドレーション → incremental hydration の流れ

3つの段階は「同じHTMLをどこまでブラウザで生かすか」の違い として整理するとわかりやすくなります。

flowchart LR
    A[リクエスト] --> B[サーバーでレンダリング<br>SSR]
    B --> C[完成済みHTMLを返す<br>すぐに表示される]
    C --> D{ブラウザでJSを実行}
    D -->|ハイドレーションなし| E[DOMを作り直す<br>ちらつき・CLSの原因]
    D -->|フルハイドレーション| F[既存DOMを再利用し<br>全コンポーネントにイベントを接続]
    D -->|incremental hydration| G[@defer 以外だけ接続<br>@defer 部分は静的HTMLのまま待機]
    G -->|viewport / interaction / idle などの条件| H[そのブロックのJSを読み込み<br>その部分だけハイドレーション]

ポイントは、incremental hydrationでは @defer の中身もサーバー側で描画されてHTMLに入っていること です。通常の @defer(クライアントでの遅延読み込み)ではサーバーは @placeholder を出力しますが、hydrate トリガー付きの場合は本物の中身が出力されます。実際のSSR出力を後で確認します。


検証環境と、SSRプロジェクトの作り方

項目 バージョン
Angular(@angular/core / CLI / @angular/ssr) 22.2.0(2026-09-23公開)
Node.js 24.21.0
計測ブラウザ Playwright 1.56 + Chromium 141

Node.js のバージョンに注意

最初に Node 22.22.2 で ng new を実行したところ、次のメッセージで止まりました。

Node.js version v22.22.2 detected.
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.

Angular CLI 22 は Node v22.22.3以上 / v24.15.0以上 / v26.0.0以上が必要 です。Node 24 に切り替えると作成できました。

プロジェクト作成(非対話)

npx -y -p @angular/cli@22 ng new hyd-demo --ssr --skip-git --defaults --style=css --skip-tests
cd hyd-demo

生成された src/app/app.config.ts には、すでに provideClientHydration() が入っています。サーバー側の app.config.server.ts は mergeApplicationConfig(appConfig, serverConfig) でこの設定を取り込むため、ブラウザとサーバーの両方に provideClientHydration() が入る 形です(公式ガイドで必須とされている条件です)。

ビルドと起動(allowedHosts に注意)

npx ng build
PORT=7141 node dist/hyd-demo/server/server.mjs

起動後に curl localhost:7141/ すると、最初は次のエラーが返りました。

ERROR: Bad Request ("http://localhost:7141/incremental").
Header "host" with value "localhost:7141" is not allowed.

For more information, see https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf

Angular SSRは、許可したホスト名以外からのリクエストを拒否します(SSRF対策)。angular.json の security.allowedHosts に許可するホストを書いてビルドし直すと表示されました。

"security": {
  "allowedHosts": ["localhost"]
}

本番では実際のドメイン名(例:"www.example.com")を入れます。


v22のincremental hydrationの書き方(v19〜21との違い)

v22では provideClientHydration() だけで incremental hydration が有効 です。インストールされた型定義(platform-browser.d.ts)にも「@deprecated Since v22.0.0, incremental hydration is enabled by default with provideClientHydration.」と書かれていました。

バージョン 書き方
v19〜v21 provideClientHydration(withIncrementalHydration())(イベント再生は自動で付く)
v22以降 provideClientHydration() だけでよい。withIncrementalHydration() は非推奨(exportは残っている)
v22で無効にしたい provideClientHydration(withNoIncrementalHydration())

withEventReplay() も incremental hydration有効時は自動で有効 になるため、公式ガイドでは「すでに書いてあれば削除してよい」とされています。既存アプリをv22に上げる場合、provideClientHydration(withIncrementalHydration(), withEventReplay()) は provideClientHydration() だけに整理 できます。


@defer (hydrate on …) のコピペ用サンプル

検証で使ったページをそのまま載せます。「いいねボタン」「コメント一覧」「フッター」の3つを、それぞれ違うトリガーで遅らせています。

// src/app/w/widgets.ts(3つの部品。実際の検証では1ファイル1コンポーネントに分けています)
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-like-button',
  template: `<button id="like" (click)="count.set(count() + 1)">いいね {{ count() }}</button>`,
})
export class LikeButton { count = signal(0); }

const COMMENTS = Array.from({ length: 300 }, (_, i) => ({ id: i + 1, body: `コメント本文 ${i + 1}:記事の感想や補足がここに入ります。` }));

@Component({
  selector: 'app-comment-list',
  template: `
    <h2>コメント({{ shown().length }}件表示)</h2>
    <button id="more" (click)="limit.set(limit() + 5)">もっと見る</button>
    <ul>@for (c of shown(); track c.id) { <li>{{ c.body }}</li> }</ul>`,
})
export class CommentList {
  limit = signal(5);
  shown = () => COMMENTS.slice(0, this.limit());
}

@Component({
  selector: 'app-site-footer',
  template: `<footer id="footer"><p>運営者情報・利用規約・お問い合わせ(静的な内容)</p></footer>`,
})
export class SiteFooter {}
// src/app/incremental.ts
import { Component } from '@angular/core';
import { LikeButton, CommentList, SiteFooter } from './w/widgets';

@Component({
  selector: 'app-incremental',
  imports: [LikeButton, CommentList, SiteFooter],
  template: `
    <h1>記事ページ(incremental hydration)</h1>

    @defer (hydrate on interaction) {
      <app-like-button />
    } @placeholder {
      <span>…</span>
    }

    <div style="height: 2000px">本文…</div>

    @defer (hydrate on viewport) {
      <app-comment-list />
    } @placeholder {
      <p>コメントを読み込み中</p>
    }

    @defer (hydrate never) {
      <app-site-footer />
    } @placeholder {
      <p>フッター</p>
    }`,
})
export class IncrementalPage {}

比較用に、同じ3部品を @defer なしで並べた /eager ページも用意しました(部品は別コピーにして、チャンクが混ざらないようにしています)。ルートはどちらも loadComponent で遅延読み込みし、app.routes.server.ts は RenderMode.Server にしています。

// src/app/app.routes.server.ts
import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  { path: '**', renderMode: RenderMode.Server },
];

サーバーが返したHTML(実際の出力を抜粋)

curl http://localhost:7141/incremental の結果です(読みやすさのため一部省略)。

<app-like-button jsaction="click:;keydown:;" ngb="d0" ngh="0">
  <button id="like" jsaction="click:;" ngb="d0">いいね 0</button>
</app-like-button><!--ngh=d0-->
<div style="height: 2000px;">本文…</div>
<app-comment-list ngh="1"><h2>コメント(5件表示)</h2>
  <button id="more" jsaction="click:;" ngb="d1">もっと見る</button>
  <ul><li>コメント本文 1:…</li> … </ul>
</app-comment-list><!--ngh=d1-->
<app-site-footer><footer id="footer"><p>運営者情報・利用規約・お問い合わせ(静的な内容)</p></footer></app-site-footer><!--ngh=d2-->
…
<script id="ng-state" type="application/json">{"__nghData__":[…],"__nghDeferData__":{"d0":{"r":1,"s":2},"d1":{"r":1,"s":2,"t":[2]},"d2":{"r":1,"s":2}}}</script>

読み取れることは次のとおりです。

出力 意味
@placeholder ではなく本物の中身が出ている hydrate付き @defer はサーバーで中身まで描画 される
jsaction="click:;" / ngb="d0" イベント再生用の目印。どの @defer(d0)に属するかを記録
ngh / __nghDeferData__ DOMの対応づけ情報と、各 @defer(d0〜d2)の状態
先頭の ng-event-dispatch-contract スクリプト JS本体より先にクリックを記録しておく小さなスクリプト

実測:どのJSがいつ読まれたか(hydrateあり/なし)

Playwrightで次の操作を順に行い、.js のレスポンスを時刻つきで記録しました(本番ビルド、localhost のためネットワーク遅延はほぼゼロです)。

  1. ページを開く(networkidle まで待つ)
  2. 「いいね」ボタンをクリック
  3. コメント欄までスクロールし「もっと見る」をクリック
  4. フッターまでスクロール

実行結果

=== /eager
like before click: いいね 0 / after click: いいね 1 / h2 after more: コメント(10件表示)
139ms   初期表示   chunk-YUeEo57C.js   2038
145ms   初期表示   main-EWSJ7O4U.js    270002
初期表示合計 272040

=== /incremental
like before click: いいね 0 / after click: いいね 1 / h2 after more: コメント(10件表示)
69ms    初期表示              chunk-Cqtk8fQr.js   1210
135ms   初期表示              main-EWSJ7O4U.js    270002
1199ms  いいねをクリック       chunk-CuBCC2Ee.js   466
2082ms  コメント欄までスクロール chunk-Cb5CPOBS.js   967
初期表示合計 271212

表にまとめると次のとおりです(サイズは非圧縮のバイト数)。

タイミング /eager(deferなし) /incremental
初期表示 main 270,002 + ページ 2,038 main 270,002 + ページ 1,210
いいねクリック時 追加読み込みなし like-button チャンク 466
コメント欄が見えた時 追加読み込みなし comment-list チャンク 967
フッターが見えた時 追加読み込みなし 読み込まれない(hydrate never)
クリック結果 いいね 0 → 1 いいね 0 → 1(最初のクリックも失われない)

わかったこと

  • @defer の中の部品は、初期表示のJSから外れ、トリガーが起きたときに個別チャンクとして読まれました。
  • hydrate never のフッターのJSは最後まで読み込まれませんでした。表示はサーバーHTMLのままです。
  • ハイドレーション前の「いいね」クリックは、チャンク読み込み後に再生されてカウントが1になりました(イベント再生が自動で有効になっている確認)。
  • 今回の部品は小さいため差は約0.8KBですが、大きな依存(グラフ描画・リッチエディタなど)を持つ部品ほど効果が大きく なります。main(約270KB)は共通なので、効くのは「ページ固有の重い部品」です。

ハイドレーション不一致(NG0500)をわざと起こしてみる

ハイドレーションは「サーバーのHTML」と「ブラウザでAngularが期待するDOM」が一致している前提で動きます。一致しないと NG0500 系のエラー になります。開発ビルド(ng build --configuration development)で起こした実際のエラーを載せます。

ケース1:<tbody> のない <table>

@Component({
  selector: 'app-bad-html',
  template: `<table><tr><td>ノートPC</td><td>12</td></tr></table>`,
})
export class BadHtmlPage {}

ブラウザはHTMLを読み込むときに <tbody> を自動で補うため、DOMの形が変わります。実際のコンソール出力です(スタックトレースは省略)。

ERROR RuntimeError: NG0500: During hydration Angular expected <tr> but found <tbody>.

Angular expected this DOM:

<table>
  <tr>…</tr>  <-- AT THIS LOCATION
  …
</table>

Actual DOM is:

<table>
  <tbody>…</tbody>  <-- AT THIS LOCATION
</table>

Note: attributes are only displayed to better represent the DOM but have no effect on hydration mismatches.

To fix this problem:
  * check the "_BadHtmlPage" component for hydration-related issues
  * check to see if your template has valid HTML structure
  * check if there are any third-party scripts that manipulate the DOM. More info: https://v22.angular.dev/guide/hydration#third-party-scripts-with-dom-manipulation
  * or skip hydration by adding the `ngSkipHydration` attribute to its host node in a template

直し方:テンプレートに <tbody> を明示します。

<table><tbody><tr><td>ノートPC</td><td>12</td></tr></tbody></table>

なお、<p> の中に <div> を入れるテンプレートも試しましたが、こちらは ビルド時点でコンパイラが止めてくれました。

✘ [ERROR] NG5002: Unexpected closing tag "p". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags

<a> の中の <a> も公式ガイドで注意点として挙げられています(今回は未検証)。

ケース2:ネイティブDOM APIで直接書き換える

@Component({
  selector: 'app-dom-widget',
  template: `<p class="msg">ウィジェット本文</p>`,
})
export class DomWidget {
  constructor() {
    const host = inject(ElementRef).nativeElement as HTMLElement;
    const badge = host.ownerDocument.createElement('b');
    badge.textContent = 'NEW';
    host.insertBefore(badge, host.firstChild); // テンプレートの外でノードを追加
  }
}

結果は次のとおりです。

ERROR RuntimeError: NG0500: During hydration Angular expected <p> but found <b>.

Angular expected this DOM:

<app-dom-widget>
  <p>…</p>  <-- AT THIS LOCATION
  …
</app-dom-widget>
…(以下、ケース1と同じ形式の案内)

さらに最終的なDOMは <b>NEW</b><b>NEW</b><p class="msg">… と バッジが2つ になりました。サーバーで1回、ブラウザで1回追加されたためです。

補足として、最初は host.prepend(badge) と書いていましたが、サーバー側で TypeError: host.prepend is not a function になりました。SSRで使われるDOMはブラウザの完全な実装ではないため、ブラウザ専用の処理はサーバーで動かさない設計が基本になります。

直し方A:afterNextRender でブラウザだけ・描画後に実行

import { Component, ElementRef, afterNextRender, inject } from '@angular/core';

@Component({
  selector: 'app-dom-widget-safe',
  template: `<p class="msg">ウィジェット本文</p>`,
})
export class DomWidgetSafe {
  constructor() {
    const host = inject(ElementRef).nativeElement as HTMLElement;
    afterNextRender(() => {
      const badge = host.ownerDocument.createElement('b');
      badge.textContent = 'NEW';
      host.insertBefore(badge, host.firstChild);
    });
  }
}
[log] Angular hydrated 3 component(s) and 10 node(s), 0 component(s) were skipped. 0 defer block(s) were configured to use incremental hydration.

エラーは出ず、DOMは <b>NEW</b><p class="msg">… と バッジ1つ になりました。afterNextRender はサーバーでは実行されず、ブラウザでハイドレーション後に1回だけ実行される ためです。

直し方B:ngSkipHydration でその部品だけ対象外にする

<app-dom-widget ngSkipHydration />
[log] Angular hydrated 2 component(s) and 7 node(s), 1 component(s) were skipped. 0 defer block(s) were configured to use incremental hydration.

「1 component(s) were skipped」と表示され、エラーは出ませんでした。 この部品だけはサーバーのDOMを捨ててブラウザで作り直すため、DOMは <b>NEW</b><p>… の1つになります。ただし再描画のぶんハイドレーションの利点は失われるので、直せない外部ライブラリ(グラフ描画など)向けの一時的な回避策 と考えるのがよいです。

ケース3:Math.random() など、サーバーとブラウザで値が変わる

@Component({
  selector: 'app-random',
  template: `<p id="rnd">{{ value }}</p>`,
})
export class RandomPage {
  value = Math.random().toFixed(4);
}
SSR HTML: 0.2155  / after hydration DOM: 0.5318

この場合はエラーが出ません。ハイドレーションが照合するのはDOMの構造(要素の並び)で、テキストの中身ではないためです。表示はサーバー値からブラウザ値へ静かに切り替わり、ちらつきの原因になります。日時・乱数・ブラウザだけで取れる値は、次の方法でそろえます。

値の種類 そろえ方
APIの結果 HttpClient(転送キャッシュが既定で有効)
乱数・サーバー時刻など サーバーで作った値を TransferState に入れて渡す
window の幅・localStorage など afterNextRender の中で読み、描画後に反映する

不一致エラーの逆引き表

エラー・症状 よくある原因 直し方
NG0500 expected <tr> but found <tbody> <table> 直下に <tr> <tbody> を明示
NG0500 expected <p> but found <b> など コンストラクタ等で appendChild / insertBefore afterNextRender に移す、Renderer2やテンプレートで表現
NG5002 Unexpected closing tag <p> の中にブロック要素 構造を直す(ビルド時に検出)
… is not a function(サーバーログ) ブラウザ専用APIをサーバーで実行 afterNextRender / isPlatformBrowser で分岐
エラーはないが数値がちらつく 乱数・日時がサーバーとブラウザで違う TransferState で値を渡す
外部ライブラリの部品だけ不一致 ライブラリがDOMを書き換える その部品に ngSkipHydration

開発ビルドでは「Angular hydrated N component(s)… N component(s) were skipped」のログが毎回出る ので、スキップ数が想定どおりかを確認する習慣をつけると安心です。


isPlatformBrowser と afterNextRender の使い分け

方法 実行される場所 実行タイミング 向いている用途
afterNextRender(() => …) ブラウザのみ 次の描画の後(1回) DOMの計測・外部ライブラリ初期化
afterEveryRender(() => …) ブラウザのみ 毎回の描画の後 スクロール位置の同期など
isPlatformBrowser(inject(PLATFORM_ID)) 判定するだけ 呼んだその場 サービス内で処理を分けたいとき

isPlatformBrowser で分岐して描画内容を変えると、それ自体が不一致の原因 になります。localStorage の読み出しなどサービス内の処理は isPlatformBrowser で、描画に関わるものは afterNextRender で「ハイドレーション後に反映」するのが安全です。


HttpClient の転送キャッシュ(二重リクエストを防ぐ)

SSRでAPIを呼ぶと、サーバーで1回、ブラウザで1回と二重に呼ばれそう に見えます。Angularは既定でサーバーの応答をHTMLに埋め込み、ブラウザでは再利用します。

// src/app/app.config.ts(providers に1行追加)
import { provideHttpClient, withFetch } from '@angular/common/http';

  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
    provideHttpClient(withFetch()),
    provideClientHydration(),
  ],
// src/app/data.ts
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';

interface Item { id: number; name: string; stock: number }

@Component({
  selector: 'app-data',
  template: `
    <h1>在庫一覧</h1>
    <ul>
      @for (i of items(); track i.id) { <li>{{ i.name }}:{{ i.stock }}</li> }
    </ul>`,
})
export class DataPage {
  items = toSignal(inject(HttpClient).get<Item[]>('/api/items'), { initialValue: [] });
}

server.ts に /api/items を追加し、呼ばれた回数をログに出して比較しました。

設定 サーバーのAPIログ ブラウザからの /api/items ng-state の中身
既定(provideClientHydration()) 1回(ua=node) 0件 応答本文(ノートPC・モニター・キーボード)が入る
provideClientHydration(withNoHttpTransferCache()) 2回(node とブラウザ) 1件 __nghData__ のみ

既定のままで二重リクエストは起きません。キャッシュ対象を調整したいときは withHttpTransferCacheOptions({ includePostRequests: true }) などで設定します(オプションは公式APIリファレンスで確認してください)。


incremental hydration を入れるときの判断表

部品の特徴 おすすめトリガー 理由
ファーストビューの外にある一覧・コメント hydrate on viewport 見えるまでJS不要
押されるまで何もしないボタン・メニュー hydrate on interaction 最初のクリックはイベント再生で失われない
ツールチップ・ホバーカード hydrate on hover マウスが乗った時点で準備
すぐ使うが優先度は低い hydrate on idle メイン処理の後に回す
操作しない静的な部品(フッター・規約文) hydrate never JSを一切読まない

よくあるつまずき

つまずき 対応
@placeholder を省いた hydrate時は使われないが、クライアントで新たに描画するときに必要なので 必ず書く
子だけハイドレーションされない 親が未ハイドレーションだと子も待つ(親から順に処理される)
hydrate when が反応しない 最上位の未ハイドレーションブロックのときだけ評価される
hydrate never の中が動かない 配下のネストも含め静的なまま。操作が必要な部品は外に出す

重い依存を探す(v22.2)

v22.2.0 では npx ng build --stats-json の出力が browser-stats.json と server-stats.json に分かれる ようになりました(実行して確認)。ブラウザ側だけを esbuild の analyze で見れば、@defer に回す候補を探しやすくなります。


よくある質問

Q. v22 で withIncrementalHydration() を書いたままでもよいですか? A. v22.2.0 の型定義にはまだ含まれていますが、v22.0.0 で非推奨になっています。incremental hydration は既定で有効なので、provideClientHydration() だけに整理しておくと将来の削除に備えられます。無効にしたい場合は withNoIncrementalHydration() を使います。

Q. withEventReplay() は必要ですか? A. incremental hydration が有効なら自動で有効になります。今回の実測でも、ハイドレーション前のクリックが再生されてカウントが0から1になりました。

Q. ハイドレーションのエラーが本番ビルドで読みにくいのですが? A. 本番ビルドではメッセージが短縮されます。ng build --configuration development でビルドしてSSRサーバーを起動すると、今回載せたような「期待したDOM/実際のDOM」付きのNG0500が表示されます。


まとめ

  • ハイドレーションはSSRのHTMLを再利用し、incremental hydrationは @defer 単位でそれを遅らせる機能 です。
  • v22では provideClientHydration() だけで incremental hydration とイベント再生が有効 になり、withIncrementalHydration() は非推奨です。
  • 実測では、@defer 内の部品のJSはクリック時・表示時に初めて読まれ、hydrate never は最後まで読まれません でした。
  • ハイドレーション前のクリックもイベント再生で失われません。
  • NG0500は <tbody> 抜けや直接DOM操作で起き、afterNextRender や ngSkipHydration で解消 できます。
  • 乱数や日時はエラーにならずに値が切り替わる ため、TransferState やHttpClientの転送キャッシュでそろえます。
  • CLI 22 は Node v22.22.3 / v24.15.0 / v26 以上 が必要で、SSRは allowedHosts の設定 も忘れずに行います。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angular SSRハイドレーション|incremental hydration実測」

株式会社RJCでは、SSRやハイドレーションのような新しいフロントエンド技術を、実際に動かして確かめながらプロジェクトの中で仲間と共有しています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?