RORK LABEN
FUNDING — Rorkが$15Mを調達。モバイル特化のノーコードAIという立ち位置に投資家の評価が集まっていますMAX-NATIVE — Rork MaxはAR/LiDAR・Metal 3D・ウィジェット・Dynamic Island・Live Activities・HealthKit・Core MLなど、React Nativeでは届かないネイティブ領域に対応MOBILE-FIRST — BoltやLovableがWebアプリ中心なのに対し、Rorkはモバイルアプリ専業。説明文から本番品質のアプリを生成しますWWDC — WWDC26が閉幕。AIがOSのコア機能となり、iOS 27世代へ。ウィジェットやLive Activitiesの価値がさらに上がる局面ですPRICING — Rorkは無料で開始でき有料$25/月〜、Rork Maxは$200/月。Expoで素早く検証し、必要に応じてMaxでネイティブ化する多段戦略が現実的ALL-APPLE — Rork MaxのSwift直接生成はiPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageまでカバーしますFUNDING — Rorkが$15Mを調達。モバイル特化のノーコードAIという立ち位置に投資家の評価が集まっていますMAX-NATIVE — Rork MaxはAR/LiDAR・Metal 3D・ウィジェット・Dynamic Island・Live Activities・HealthKit・Core MLなど、React Nativeでは届かないネイティブ領域に対応MOBILE-FIRST — BoltやLovableがWebアプリ中心なのに対し、Rorkはモバイルアプリ専業。説明文から本番品質のアプリを生成しますWWDC — WWDC26が閉幕。AIがOSのコア機能となり、iOS 27世代へ。ウィジェットやLive Activitiesの価値がさらに上がる局面ですPRICING — Rorkは無料で開始でき有料$25/月〜、Rork Maxは$200/月。Expoで素早く検証し、必要に応じてMaxでネイティブ化する多段戦略が現実的ALL-APPLE — Rork MaxのSwift直接生成はiPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageまでカバーします
記事一覧/開発ツール
開発ツール/2026-06-13中級

「さくら」で検索すると「サクラ」が出てこない — Rork 製アプリのアプリ内検索と日本語の正規化

ひらがなで検索するとカタカナのタイトルが出てこない——個人開発の壁紙アプリで踏んだ日本語検索の取りこぼしを、NFKC 正規化・かな変換・事前インデックス化の3手順で解消した実装メモです。Rork への指示文例も載せています。

Rork386検索日本語対応正規化Expo63React Native155

個人開発で運用している壁紙アプリに検索ボックスを付けたときのことです。App Store に出す前に家族に触ってもらったところ、「さくらで検索しても何も出てこない」と言われました。データ側のタイトルは「サクラ」。人間にとっては同じ言葉でも、プログラムにとっては別の文字列です。

Rork に「検索機能を付けてください」と頼むと、検索そのものは数分で動き始めます。ただ、この表記ゆれの問題は、こちらから仕様として伝えない限り、まず解決されません。実際にどこでつまずき、どう直したのかを、動くコードと Rork への指示文例つきで残しておきます。

Rork が最初に生成する検索は、どこまで動くのか

「検索機能を追加して」とだけ伝えたとき、Rork が生成するのはおおむね次のような実装です。

const [query, setQuery] = useState("");
 
const results = items.filter((item) =>
  item.title.toLowerCase().includes(query.toLowerCase())
);

部分一致と、英字の大文字・小文字の吸収までは最初から入っています。英語圏のアプリなら、これで十分実用になります。AI は「検索」という言葉から英語圏の標準的な実装を導くので、ここまでは速いのです。

一方で、日本語のデータに対しては次の取りこぼしが残ります。

  • 「さくら」と「サクラ」(ひらがなとカタカナ)
  • 「ABC」と「ABC」(半角と全角の英数字)
  • 「サクラ」と「サクラ」(半角カナと全角カナ)
  • 「夜 景」と「夜景」(間に挟まった空白)

私のアプリの場合、壁紙のタイトルはカタカナ、タグはひらがな、というように人間が入力した時点で表記が揺れていました。蓄積済みのデータを統一し直すより、検索時に吸収するほうが現実的です。

正規化関数を1つ用意して「同じ言葉」に潰す

方針はシンプルで、比較の直前にクエリとデータの両方を同じルールで変換するだけです。この変換を正規化と呼びます。次の関数をそのまま使えます。

// utils/searchNormalize.ts
export function normalizeForSearch(input: string): string {
  return input
    .normalize("NFKC") // 全角英数→半角、半角カナ→全角カナに統一
    .toLowerCase() // 英字を小文字に統一
    .replace(/[ぁ-ゖ]/g, (ch) =>
      // ひらがな→カタカナ(コード位置が 0x60 ずれているだけ)
      String.fromCharCode(ch.charCodeAt(0) + 0x60)
    )
    .replace(/[\s ]+/g, ""); // 半角・全角の空白を除去
}

ポイントは1行目の normalize("NFKC") です。Unicode には「見た目が似た別の文字」を統一形へ変換する仕組みが標準で用意されており、全角の「ABC」は「ABC」に、半角の「サクラ」は「サクラ」になります。自前で変換表を持つ必要はありません。

ひらがなとカタカナの変換だけは NFKC では行われないため、文字コードのオフセットを使って自分で寄せます。ひらがなとカタカナは Unicode 上でちょうど 0x60 ずれて並んでいるので、置換は1行で済みます。

この関数を通すと、次の3つはすべて同じ文字列「サクラ」になります。

normalizeForSearch("さくら"); // "サクラ"
normalizeForSearch("サクラ"); // "サクラ"
normalizeForSearch("サ クラ"); // "サクラ"

正規化は「入力のたび」ではなく「読み込み時に1回」

正規化関数ができたら、filter の中で両方を変換すれば動きます。ただし、そのまま書くと「1文字入力するたびに、全アイテムを正規化し直す」コードになります。

私のアプリは約 1,200 件のデータでしたが、古めの Android 実機では入力がワンテンポ遅れる感触がありました。原因は明らかで、キー入力のたびに 1,200 回の正規化が走っていたためです。データ側の正規化は読み込み時に1回だけ済ませ、検索キーとして持たせておきます。

// hooks/useWallpaperSearch.ts
import { useMemo, useState, useDeferredValue } from "react";
import { normalizeForSearch } from "../utils/searchNormalize";
 
type Wallpaper = { id: string; title: string; tags: string[] };
 
export function useWallpaperSearch(items: Wallpaper[]) {
  const [query, setQuery] = useState("");
  // 入力欄の反応を最優先し、結果リストの描画は少し後回しにする
  const deferredQuery = useDeferredValue(query);
 
  // データ側の正規化は読み込み時に1回だけ
  const indexed = useMemo(
    () =>
      items.map((item) => ({
        item,
        key: normalizeForSearch(item.title + " " + item.tags.join(" ")),
      })),
    [items]
  );
 
  const results = useMemo(() => {
    const q = normalizeForSearch(deferredQuery);
    if (!q) return items;
    return indexed
      .filter((entry) => entry.key.includes(q))
      .map((entry) => entry.item);
  }, [indexed, deferredQuery, items]);
 
  return { query, setQuery, results };
}

useDeferredValue は React 18 の標準フックで、追加ライブラリなしに描画の優先度付けをしてくれます。数千件程度のクライアント内検索なら、事前インデックス化とこのフックの組み合わせで十分滑らかに動く、というのが私の実感です。逆に、この規模のために検索サーバーや外部サービスを導入するのは過剰だと考えています。

結果ゼロの画面を「行き止まり」にしない

正規化を入れても、結果が0件になることはあります。そのとき空白の画面だけが残ると、ユーザーには故障と区別がつきません。FlatList の ListEmptyComponent で、見つからなかった事実と、次にできる操作の2つだけは返すようにしています。

<FlatList
  data={results}
  keyExtractor={(w) => w.id}
  renderItem={({ item }) => <WallpaperCard wallpaper={item} />}
  ListEmptyComponent={
    isLoading ? null : (
      <View style={{ padding: 32, alignItems: "center" }}>
        <Text>「{query}」に合う壁紙が見つかりませんでした</Text>
        <Pressable onPress={() => setQuery("")}>
          <Text style={{ color: "#6366f1", marginTop: 12 }}>
            検索条件をクリアして全件に戻る
          </Text>
        </Pressable>
      </View>
    )
  }
/>

1つ注意があります。ListEmptyComponent はデータがまだ届いていない読み込み中にも表示されるため、isLoading 中は出さないように分岐しないと、起動直後に「見つかりませんでした」が一瞬チラつきます。リストが空に見える問題の切り分けは FlatList が空白で何も映らない—Rork アプリのリスト崩れを切り分ける に、読み込み中の見せ方は Rork アプリでスケルトンスクリーンを実装する — 読み込み中の体感速度を改善する実践パターン にまとめています。

Rork への指示文 — 仕様を日本語で言語化して渡す

ここまでの内容は、最初から Rork に伝えておけば生成段階で組み込んでもらえます。私が実際に使っている指示文をそのまま載せます。

検索機能を追加してください。要件は次の通りです。
 
1. タイトルとタグを対象に部分一致で絞り込む
2. 比較の前に、クエリとデータの両方を同じルールで正規化する
   - Unicode NFKC 正規化(全角英数→半角、半角カナ→全角)
   - 英字は小文字に統一
   - ひらがなはカタカナに変換してから比較
   - 半角・全角の空白は無視
3. データ側の正規化は読み込み時に1回だけ行い、
   入力のたびに全件を正規化しない
4. 結果が0件のときは、検索語をクリアするボタンを表示する
5. 読み込み中は0件表示を出さない

「検索機能を付けて」と一言で頼んだ場合と比べると、生成されるコードの差は歴然です。ノーコードツールを使っていても、表記ゆれのような自国語特有の要件を仕様として言語化する部分は、依然として人間の仕事だと私は考えています。むしろここが、個人開発者がアプリの使い心地で差をつけられる場所ではないでしょうか。

もう1つ、Rork が生成した検索欄を仕上げる際に私自身よく手を入れるのが、TextInput の属性です。日本語入力では OS の自動大文字化や自動修正が誤作動の元になりやすいため、autoCapitalize="none"autoCorrect={false} を指定し、確定操作はキーボードの検索ボタンに任せる returnKeyType="search" を明示しています。生成直後のコードにはこの3つが入っていないことが多く、地味ですが入力の気持ちよさが変わる部分です。

なお、検索欄はキーボードが常に出入りする UI です。入力欄がキーボードに隠れる問題に当たったら、Rorkアプリでキーボードが入力フィールドを隠す問題の完全対処ガイド が参考になるはずです。

次の一歩 — 自分のアプリで「取りこぼし」を1つ探す

手元のアプリに検索欄があるなら、「ひらがなでカタカナのデータを引けるか」「全角英数で英字のデータを引けるか」の2つを試してみてください。1つでも取りこぼしがあれば、normalizeForSearch を入れる価値があります。データ側が綺麗に統一されているアプリほど後回しになりがちですが、ユーザーの入力までは統一できない——というのが、検索を付けてみて私が学んだことでした。

この10行ほどの正規化関数が、どなたかのアプリの検索ボックスをすこし賢くするきっかけになれば嬉しいです。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-06-13
Rork 製 Expo アプリを Kotlin ネイティブへ移せる構造にしておく — Android Studio 移行エージェント発表を受けた設計の見直し
Android Studio の React Native→Kotlin 自動移行エージェント発表を受け、Rork 製 Expo アプリを「移せる構造」に保つ設計を整理。ネイティブ依存の棚卸しスクリプト、コア層の分離パターン、移行準備度チェックリストを実例つきで紹介します。
開発ツール2026-06-12
Android 17 で「ポートレート固定」が通用しなくなる — Rork 製 Expo アプリの大画面対応を前倒しで済ませる手順
Android 17 では大画面デバイスでの画面固定・リサイズ制限が無視されるようになります。Rork で生成した Expo アプリの影響判定からレイアウト改修、エミュレータだけで済ませる検証手順までを実例ベースでまとめました。
開発ツール2026-06-12
Rork 製アプリに開発者デバッグメニューを実装する — 広告・課金・Remote Config の検証をリリース前に終わらせる設計
リリースビルドでしか再現しない広告・課金・Remote Config の不具合に毎回本番で向き合うのは消耗します。壁紙アプリ6本の運用で固まった、ストア提出ビルドには存在ごと残さない開発者デバッグメニューの設計と実装を、動くコードと落とし穴付きで共有します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →