Micro Frontend: אתגר הפוקוס בין Angular ל-React בתוך iframe
סער טויטו
Software & Web Development
16 באפריל 2026
•
8 דקות קריאה
לפעמים דווקא הדרישות הקטנות הן אלה שחושפות את הפינות העדינות באמת בארכיטקטורה. במקרה הזה, מה שנראה כמו משימה זניחה - להעביר פוקוס אוטומטי לשדה הראשון של קוד OTP - הפך לתרגיל בהבנה עמוקה של איך באמת זורם פוקוס בדפדפן, במיוחד כששתי שכבות Framework שונות חיות זו בתוך זו.
רגע לפני שמתחילים: מה זה בכלל Micro Frontend
לפני שצוללים לאתגר עצמו, חשוב ליצור בסיס משותף ולהבין את ההקשר הטכנולוגי. Micro Frontend הוא ארכיטקטורה שמאפשרת לפצל מערכת Frontend גדולה למספר אפליקציות קטנות ועצמאיות. כל חלק יכול להיבנות בטכנולוגיה שונה, להתנהל באופן עצמאי, ולהיות באחריות של צוות אחר - כל זאת תוך שמירה על חוויית משתמש אחידה למשתמש הקצה.
הרעיון מקביל ל-Microservices בעולם ה-Backend: במקום מונולית אחד גדול שכל שינוי קטן בו מצריך פריסה של כל המערכת, מפצלים אותו לחלקים שניתן לפתח, לבדוק ולשחרר באופן עצמאי. בעולם ה-Frontend זה מאפשר, למשל, שצוות אחד יעבוד ב-React על מודול תשלומים בזמן שצוות אחר עובד ב-Angular על מודול ניהול חשבון - באותו אתר, באותה חוויה.
איך זה נראה בפועל
קיימות מספר דרכים לטעון Micro Frontend בתוך אפליקציה מארחת (Host):
iframe
הרכיב נטען בתוך iframe פנימי, מה שיוצר בידוד מלא בין השכבות - גם של JavaScript וגם של CSS. הגישה הוותיקה והבטוחה ביותר, אבל גם זו שמייצרת הכי הרבה אתגרי תקשורת בין השכבות.
Module Federation
מנגנון של Webpack שמאפשר לטעון מודולים מאפליקציה אחת לתוך אחרת בזמן ריצה, ללא iframe. הוא מאפשר אינטגרציה הדוקה יותר אבל דורש שיתוף תלויות.
Web Components
שימוש ב-Custom Elements כדי לעטוף כל Micro Frontend בתוך תג HTML עצמאי, עם בידוד באמצעות Shadow DOM.
במקרה שבו נתקלנו, האפליקציה המארחת השתמשה דווקא בגישת ה-iframe - וזה מה שהפך אתגר פוקוס פשוט לכאורה למשהו עמוק יותר.
הדרישה: פוקוס אוטומטי לשדה OTP
במהלך אחת המשימות עלתה דרישה שנשמעת טריוויאלית: בעמוד שבו המשתמש מתבקש להזין קוד OTP, השדה הראשון של הקוד צריך לקבל פוקוס באופן אוטומטי מיד עם הניווט לעמוד. כך המשתמש יוכל להתחיל להקליד את הקוד מהשנייה הראשונה - בלי לחיצה נוספת, בלי חיכוך, בלי מחשבה.
זו בדיוק הדוגמה הקלאסית למה ש-UX טוב נראה: דרישה שאם היא מתבצעת היטב, אף אחד לא יחשוב עליה. אבל אם היא לא עובדת - המשתמש מרגיש את זה מיד.
מה היה צריך לקרות מבחינה טכנית
במצב רגיל, ב-React, מימוש הדרישה הזו הוא שורות בודדות:
בכל אפליקציית React רגילה - זה היה עובד מיד. אבל לא במקרה שלנו.
הארכיטקטורה: שתי שכבות, שני עולמות
האתגר נבע ישירות מאופן הבנייה של המערכת:
האפליקציה הראשית - Angular
כל ה-Host נכתב ב-Angular. הוא אחראי על הניווט, על תפריטי המערכת, על ה-Layout הכללי, ועל טעינת ה-Micro Frontends השונים.
ה-Micro Frontend - React בתוך iframe
הרכיב שאחראי על תהליך הזיהוי, כולל מסך ה-OTP, נכתב ב-React. הוא נטען בתוך iframe פנימי שמשובץ באפליקציית Angular.
המשמעות בפועל: רכיב ה-OTP שצריך לקבל פוקוס נמצא בתוך iframe, אבל ההקשר הראשי של הדפדפן (window הראשי, ה-document שעליו המשתמש "התלבש") שייך לאפליקציית Angular החיצונית.
וזה ההבדל הקריטי שלא חשבנו עליו בהתחלה.
הניסיון הראשון: לעשות את זה רק מתוך React
כמו שכל מפתח React היה עושה, התחלנו מהמקום המובן מאליו: הוספנו useEffect ב-Component של ה-OTP, ובאמצעות ref ביצענו focus על השדה הראשון מיד עם הטעינה.
במבט ראשון - הכל נראה היה תקין. הסטייל של השדה השתנה. צבע המסגרת הפך למצב active. הפלסהולדר נעלם. כל הסימנים החיצוניים שצועקים "השדה הזה בפוקוס" היו שם.
אבל ברגע שהמשתמש ניסה להקליד - שום דבר לא קרה. המקלדת לא התחברה לשדה. ההזנה שלו פשוט נעלמה לחלל.
הרגע שבו הבנו שמשהו עמוק יותר קורה
הסיבה התבררה כיסודית ונוגעת לאופן שבו הדפדפן עצמו מנהל פוקוס: פוקוס בדפדפן הוא לא דבר אחד - הוא מתפצל לשתי רמות.
פוקוס ברמת ה-Document
זהו הפוקוס הפנימי בתוך מסמך מסוים. הוא קובע איזה אלמנט ב-DOM "מקבל" את ההקלדה, ומה שיצוין ב-document.activeElement.
פוקוס ברמת ה-Window/Frame
זהו הפוקוס ברמה הגבוהה יותר - איזה חלון או iframe בכלל מקבל את אירועי המקלדת מהמערכת ההפעלה. רק אחד יכול להחזיק בפוקוס הזה בכל רגע נתון.
מה שקרה אצלנו: ה-React הפנימי קרא ל-focus() על השדה - מה שהשפיע על document.activeElement בתוך ה-iframe. אבל הדפדפן עדיין החזיק את הפוקוס ברמה החיצונית באפליקציית Angular. ה-iframe בכלל לא נחשב כ"חלון הפעיל". לכן השדה נראה ויזואלית כמו שהוא בפוקוס - אבל המקלדת המשיכה לדבר עם השכבה החיצונית.
הפתרון: להתחיל מהשכבה הנכונה
מכאן הבנו שהפתרון לא יכול להגיע מתוך React לבדו. הוא חייב להתחיל דווקא מהשכבה של Angular - מהאפליקציה המארחת. הסדר הנכון של הפעולות הוא קריטי כאן:
שלב 1: ה-Host מעביר פוקוס ל-iframe
אפליקציית Angular היא זו שצריכה לקרוא ל-focus() על אלמנט ה-iframe עצמו. זה מה שמודיע לדפדפן: "מהרגע הזה, אירועי המקלדת הולכים לחלון שבתוך ה-iframe".
שלב 2: React מבצע פוקוס פנימי על השדה
רק אחרי שה-iframe קיבל את הפוקוס ברמת החלון, ה-React שבתוכו יכול לקרוא ל-focus() על השדה הספציפי - וזה כבר באמת יעבוד, כי המקלדת מחוברת לחלון הנכון.
שתי דרכים מקובלות ליצור את התיאום הזה
העברת event ייעודי מה-Host אל ה-Micro Frontend
אפליקציית Angular שולחת postMessage ל-iframe ברגע שהיא הפעילה את הפוקוס שלו. ה-React מקשיב להודעה, ובתגובה מפעיל את הפוקוס הפנימי על השדה. הגישה הזו היא הנקייה ביותר, כי היא מתואמת ולא נשענת על ניחושים לגבי תזמון.
מנגנון Retry / Listener בצד של React
הצד של React מקשיב לאירוע focus על ה-window שלו (ה-iframe), ורק אז מפעיל את הפוקוס הפנימי על השדה. גישה זו טובה כשאי אפשר לשנות את ה-Host בקלות, או כשרוצים שה-Micro Frontend יהיה עצמאי לחלוטין.
בכל אחת מהדרכים - העיקרון נשאר זהה: קודם פוקוס לחלון, אחר כך פוקוס לשדה. סדר הפוך לא יעבוד, גם אם הוא נראה כאילו עבד.
Same-Origin לעומת Cross-Origin: הבדל שמשנה את כללי המשחק
חשוב להבחין בין שני תרחישים שמשפיעים על איזה פתרון בכלל זמין:
Same-Origin (אותו דומיין, פרוטוקול ופורט)
כש-ה-Host וה-Micro Frontend על אותו origin, ל-Angular יש גישה מלאה ל-DOM שבתוך ה-iframe דרך iframe.contentWindow ו-iframe.contentDocument. במצב הזה, גם קריאה ישירה לפוקוס מתוך ה-Host וגם postMessage עובדים. המקרה שלנו השתייך לקטגוריה הזו, ולכן יכולנו לבחור בפתרון הנקי ביותר.
Cross-Origin (דומיין/פרוטוקול/פורט שונים)
כש-ה-iframe נטען מ-origin שונה, מדיניות ה-Same-Origin Policy של הדפדפן חוסמת כל גישה ישירה ל-DOM הפנימי. במצב הזה postMessage היא לא רק אופציה - היא הדרך היחידה לתאם בין השכבות. ה-Host יכול עדיין לקרוא ל-iframe.focus() כדי להעביר את הפוקוס לרמת החלון, אבל הפעלת focus() על שדה ספציפי חייבת לקרות מבפנים, על ידי ה-React שמקשיב להודעה.
אם המערכת שלכם עשויה לעבור ל-cross-origin בעתיד (למשל, Micro Frontend שיתארח בסאב-דומיין נפרד או ב-CDN חיצוני) - שווה לתכנן את הארכיטקטורה כבר עכשיו עם postMessage, גם אם בפועל הכל same-origin. זה יחסוך כתיבה מחדש כשהדרישות ישתנו.
למה זה קורה דווקא בארכיטקטורת Micro Frontend
חשוב להבין שהבעיה הזו לא ייחודית לצמד Angular ו-React. היא תופיע בכל פעם שיש iframe שמכיל אפליקציה והדפדפן צריך להחליט "מי שומע את המקלדת". כל קומבינציה - Vue בתוך React, אפליקציה לגאסי בתוך אפליקציה חדשה, או אפילו שתי אפליקציות באותה טכנולוגיה - תיתקל באותו אתגר.
המאפיין המרכזי של ארכיטקטורת Micro Frontend הוא הבידוד. זה היתרון הגדול שלה - כל צוות עובד באופן עצמאי, ללא תלויות, ללא דריסות סגנון, ללא קונפליקטים בין גרסאות של ספריות. אבל הבידוד הזה הוא גם המקור לאתגרים. כל פעם שצריך לתאם פעולה בין השכבות - האם זה פוקוס, ניווט, מצב משותף, או אירועים גלובליים - חייבים פרוטוקול מסודר של תקשורת.
הלקח הרחב יותר
כאשר עובדים ב-Micro Frontend, ההבחנה החשובה היא לא רק "מי כותב את הקוד" אלא "מי בעצם רץ ברגע נתון". הדפדפן רואה iframe לא כחלק מההיררכיה של הדף, אלא כחלון נפרד לחלוטין. כל אינטראקציה שדורשת מעבר בין החלונות הללו - ובמיוחד אינטראקציות שתלויות במצב פנימי של הדפדפן כמו פוקוס, selection, או clipboard - חייבת לעבור דרך השכבה החיצונית.
סיכום
כך למעשה פתרנו בעיית פוקוס שנוצרה כתוצאה ממעבר בין שתי שכבות Framework שונות בתוך ארכיטקטורת Micro Frontend. ההבנה שהפתרון לא יכול לבוא רק מצד אחד של המשוואה - אלא חייב להיות שיתוף פעולה בין ה-Host לבין ה-Micro Frontend - הייתה נקודת המפנה.
הסיפור הזה ממחיש משהו עמוק יותר על עבודה בארכיטקטורות מורכבות: לפעמים הבאג הוא לא בקוד שכתבת, אלא בהנחה הסמויה שעמדה מאחוריו. ההנחה שלנו הייתה ש-focus() ב-React מקבל פוקוס "אמיתי". המציאות הייתה שהפוקוס הזה היה נכון רק בתוך הקונטקסט שלו, לא במערכת כולה.
אם אתם בונים, מתחזקים או חושבים על ארכיטקטורת Micro Frontend - שווה לזכור: כל פעולה שנשמעת פשוטה במונולית הופכת לעיתים לתרגיל בתיאום בין שכבות. תכננו את פרוטוקול התקשורת בין השכבות מההתחלה, לא רק כשהבעיה מגיעה.