Embedding Reactjs in Remote Site iFrame

14,372

Solution 1

To satisfy the Same Origin policy (prevent CORS errors), set the <iframe>'s srcdoc attribute instead of trying to write to it.

n.srcdoc = "\n    <!doctype html>\n    <head><script src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></head>\n    <body><div id='root'></div></body>\n    </html>";

As an added bonus, you can disable the right-click context menu with:

n.contentWindow.document.addEventListener("contextmenu", function(e){
    e.preventDefault();
    return false;
}, false);

This is completely useless as a security measure, but it's only there as a red herring. The page that shows when you open the frame on its own will not contain the content. Only do this if there's nothing in the iframe's page that the user will want to copy; it doesn't stop them from doing so, but it's really irritating if you do want to copy something.


Demo:

//Creates Div
var d = document.createElement("div");
document.body.appendChild(d);

//Creates iFrame
var n = document.createElement("iframe");
n.id = "microcom-frame";
n.style.width = "100%";
n.style.height = "100%";
n.style.background = "transparent";
n.style.position = "relative";
n.style.margin = 0;
n.style.border = "none";
n.style.overflow ="hidden";
n.style.display = "block";

//Append iFrame inside Div
d.appendChild(n);

//Write content to iFrame
n.srcdoc = "<!doctype html><html><head></head><body><div id='root'>Example content</div></body></html>";

Solution 2

Embedding in iframe is not a good practice as for security purposes, it could be better if you use sandboxed iframe .

Solution 3

The script fire before the target div is read in DOM in your test environment, just change the order of the tags. See below and see the bottom for other issues what you are trying to do will probably cause when moved to the wild.

Add "async defer" to the script tag you insert in the iframe in your your initial code like so:

n.contentWindow.document.write("\n    <!doctype html>\n    <head><script async defer src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></head>\n    <body><div id='root'></div></body>\n    </html>"),

You can just add the script tag at the end of the body you insert instead (or do booth that and put async defer on it)

Try it with srcdoc, it works even better since it doesn't even give the option to break out of the frame (which just reloads the current location in the example above)

n.srcdoc = "\n    <!doctype html>\n    <head></head>\n    <body><div id='root'></div><script async defer  src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></body>\n    </html>"

However.

When I tried this in production it didn't load my entire app. Anything other then the inital js bundle from the /static folder in build would not load because the app looks for them relative to the widgets location.

You might solve this by making sure the js does not chunk (create react app should not split your code yet anyway, unless you are using a package to do specifically that). You also need to load all css-files and images externally in your app rather then importing them.

It may also be possible to use one of the methods for deploying a react app into a sub-directory to set an absolute base url to your app's static resources.

Also you probably want to remove registerServiceWorker() from index.js in the react app when using it like this.

If that still causes issues and you need to load it by src you could have the react app check if it is not inside a frame and kill itself or go back when unframed by some of the methods here How to identify if a webpage is being loaded inside an iframe or directly into the browser window?

Solution 4

You have used root as the target container in index.js. Probably this is not the name of DOM element in index.html file. Make it the same and probably it will work.

Share:
14,372
Someone Special
Author by

Someone Special

A real estate agent by day, and a PHP developer by night. Currently concurrently managing 3 PHP web projects that involves team of more than 35. Real Estate - Focus mainly on overseas projects and launched simple websites using wordpress, or one page using PHP. The Bridge - Guaranteed Rental Yield of 70% over the next 10 years from USD$141 onwards. Own a retail shop in one of the established mall in the capital center and collect rental income for next 10 years from Singapore Listed leading developer. High Appreciation. Grandeur Park Residences - Singapore latest launched project within the MRT line at Tanah Merah by CEL Development Earlybird Properties - a wordpress run website that show case Royal Wharf, The Peak Cambodia and The Bridge Cambodia. The Peak Cambodia - A mix development of Residential, Business Tower, Shopping Mall, within the same development as Shangri-La Hotel (World renowned 5 Star Hotel).

Updated on July 26, 2022

Comments

  • Someone Special
    Someone Special almost 2 years

    Updated for Bounty

    I am currently using src="url" in my iframe to load the ReactApp which is not optimal for me as that will allow user to right-click and "Open In New Window".

    The optimal solution which I'm looking for is to write my bundle.js into the iframe together with a or simliar solution. The src will remain as blank so user cannot conveniently right click to open in new window/tab.

    I am exploring how to use React to make an embeddable widget so I got the following so far from googling. However, my doesn't render, and returns the following message.

    Error Message [Error] Invariant Violation: Target container is not a DOM element.

    I use create-react-app for my embeddable app and I have only 2 simple files.

    index.js

    import React from 'react';
    import ReactDom from 'react-dom';
    import App from './App';
    
    ReactDom.render(
            <App />, document.getElementById('root')
    )
    

    App.js

    import React, { Component } from 'react';
    
    class App extends Component {
        render() {
            return (
                <div>This is an embedable widget</div>
                )
        }
    }
    
    export default App;
    

    I created a test.js which will be called in the remote iframe with the simple codes below to generate the html, to include the js bundle, as well as the div with id.

    This is the test.js

    //Creates Div
    var d = document.createElement("div");
    document.body.appendChild(d);
    
    //Creates iFrame
    var n = document.createElement("iframe");
    n.id = "microcom-frame";
    n.style.width = "100%";
    n.style.height = "100%";
    n.style.background = "transparent";
    n.style.position = "relative";
    n.style.margin = 0;
    n.style.border = "none";
    n.style.overflow ="hidden";
    n.style.display = "block";
    
    //Append iFrame inside Div
    d.appendChild(n);
    
    //Write content to iFrame
    n.contentWindow.document.open("text/html", "replace"), 
    n.contentWindow.document.write("\n    <!doctype html>\n    <head><script src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></head>\n    <body><div id='root'></div></body>\n    </html>"), 
    n.contentWindow.document.close();
    

    Now on the remote site I just have the following script in the header to call my test.js above.

        <script>
          (function() {
      var d = document,
          h = d.getElementsByTagName('head')[0],
          s = d.createElement('script');
    
      s.type = 'text/javascript';
      s.async = true;
      s.src = 'http://localhost:3001/test.js';
      h.appendChild(s);
      
    } )();
    </script>
    

    Now is this....

    • test.js loaded successfully
    • div created successfully
    • iframe created successfully
    • i can see bundle.js added in the header of iframe together with the div with id root

    However, is not displaying.

    Appreciate someone can bring me to the next step. I am using create-react-app to create my react files (that's the only way i learnt.)

  • Someone Special
    Someone Special about 6 years
    Hi it still doesn't load the app.
  • wizzwizz4
    wizzwizz4 about 6 years
    @SomeoneSpecial What does the JavaScript console say? Have you modified it so that it is loading the app instead of Example content?