reactjs – NGINX Setup to Match Proxy Middleware Configuration

I have the following setupProxy.js class that configures redirection for api calls to my server.

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
    app.use(
        "/api/tours",
        createProxyMiddleware({
            target: "http://localhost:5000", 
            changeOrigin: true,
        })
    );
};

My App.js looks like

const App = () => {

    const { userDetails } = useContext(AuthContext);
    const { colorMode } = useContext(ColorModeContext);

    let currentTheme = React.useMemo(() =>
        createTheme(deepmerge(getDesignTokens(colorMode)), getThemedComponents(colorMode)),
        [colorMode]
    );
    currentTheme = responsiveFontSizes(currentTheme);
 
    return (
        <ThemeProvider theme={currentTheme}>
            <CssBaseline />
            <Router>
                <Switch>
                    <AuthRoute exact path="/" component={HomePage} />
                    <AuthRoute path="/home" component={HomePage} />
                    <Route path="/public/:id" component={PlayerPage} />
                    <AuthRoute path="/tours/:id" component={PlayerPage} />
                    <Route path="/login">
                        {userDetails == null ? <LoginPage /> : <Redirect to="/home" />}
                    </Route>
                    <Route component={FourOhFour} />
                </Switch>
            </Router>
        </ThemeProvider>
    );
};
export default App;

and I can navigate to my PlayerPage using a url like

localhost:3000/tours/eef67wsrr899iop009

This loads fine with the PlayerPage looking like

const PlayerPage = () => {

    const history = useHistory();

    const { id } = useParams();
    const mode = LocalStorgaeCache.getItem(APP_COLOR_MODE_KEY);
    const theme = createTheme(deepmerge(getDesignTokens(mode), getThemedComponents(mode)));
    
    const { userDetails } = useContext(AuthContext);
    const { tourDetails, isLoading: isLoadingTour, dispatch } = useContext(TourContext);

    const [loading, setLoading] = useState(true);
    const [showTagInfo, setTagId] = useState(0);
    const [tagInfoWindowVisible, showTagInfoWindow] = useState(false);
    const [results] = useState([]);
    const [setTour] = useState();
    const showTagging = useRef(false);
    const [showScan, setShowScan] = useState(false);
    const [addTag, setAddTag] = useState(false);
    const [tagging, setTagging] = useState(false);
    const [myPos, setPos] = useState([]);

    const myLayer = useRef(0);
    const tagEditing = useRef(false);
    const refreshTag = useRef(false);
    const [myTagNumber, setTagNumber] = useState(0);

    const [showPosInfo, setPosInfo] = useState('');
    const [snackMessage, setSnackMessage] = useState('');
    const [isMobile, setIsMobile] = useState(false);

    const [myTags, setTags] = useState([{}]);
    const [watermark, setWatermark] = useState();

    useEffect(() => {
        return () => {
            dispatch(clearLoadedTour());
        };
    }, []);

    useEffect(() => {
        setIsMobile(navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i));

        const actualLoad = async () => {

            if (userDetails == null) { 
                history.push("/login");
            }
            await getTour(userDetails, id, dispatch);
            setLoading(false);
        };

        if (tourDetails == null) {
            actualLoad();
        }
        else {
            const simulateLoad = async () => {
                await new Promise((r) => setTimeout(r, 1000));
                setLoading(false);
            };
            simulateLoad();
        }
    }, [dispatch, history, id, tourDetails, userDetails]);

    return (
        <ThemeProvider theme={theme}>
            <Fragment>
                <Backdrop
                    sx={{ color: "white", zIndex: (theme) => theme.zIndex.drawer + 1 }}
                    open={loading}>
                    <CircularProgress color="inherit" />
                </Backdrop>
                { tourDetails != null ? (
                <div>
                    {!watermark && <Watermark />}
                    <SimpleSnackBar snackMessage={snackMessage} />
                    <InfoCard
                        tagEditing={tagEditing}
                        setTagging={setTagging}
                        setShow={showTagInfoWindow}
                        refreshTag={refreshTag}
                        setTour={setTour}
                        setShowScan={setShowScan}
                        results={results}
                        tagNumber={myTagNumber}
                        tags={myTags}
                        tour={tourDetails?.viewformTour}
                        pos={myPos}
                        setAddTag={setAddTag}
                        cb={showPosInfo}
                        st={showTagInfo}
                    />
                    {tagInfoWindowVisible && (
                        <MediaCard
                            tagEditing={tagEditing}
                            tagId={showTagInfo}
                            setShow={showTagInfoWindow}
                        />
                    )}
                    <Player
                        token={tourDetails?.azureServiceSasToken}
                        tour={tourDetails?.viewformTour}
                        account={tourDetails?.azureStorageUri}
                        setSnackMessage={setSnackMessage}
                        layer={myLayer}
                        tagging2={tagging}
                        refreshTag={refreshTag}
                        setTagId={setTagId}
                        showTagInfoWindow={showTagInfoWindow}
                        setPosInfo={setPosInfo}
                        setLoading={setLoading}
                        setNewTagPosition={setPos}
                        tagEditing={tagEditing}
                    />
                </div>
                ) : ( <></> ) }
            </Fragment>
        </ThemeProvider>
    );
};
export default PlayerPage;

The problem is, when I deploy this client and server with NGINX as the reverse-proxy and load balancer, the

myapp.com/tours/eef67wsrr899iop009

does not work, in stead of asking the client to render this, the request is going straight to the server and I have no client side rendering setup. The NGINX configuration file looks as follows

worker_processes auto;

events {
  worker_connections 1024;
}

pid /var/run/nginx.pid;

http {

    include mime.types;

    upstream loadbalancer {
        server server:5000 weight=3;
    }

    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        port_in_redirect off;
        absolute_redirect off;

        return 301 https://$host$request_uri;
    }

    server {
        listen [::]:443 ssl;
        listen 443 ssl;

        server_name myapp.app* myapp.co* myapp-dev.uksouth.azurecontainer.io* localhost*;
        error_page 497 https://$host:$server_port$request_uri;

        error_log /var/log/nginx/client-proxy-error.log;
        access_log /var/log/nginx/client-proxy-access.log;

        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
        ssl_prefer_server_ciphers  on;
        ssl_session_cache          shared:SSL:10m;
        ssl_session_timeout        24h;

        keepalive_timeout 300;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

        ssl_certificate     /etc/nginx/certificate.crt;
        ssl_certificate_key /etc/nginx/private.key;

        root /usr/share/nginx/html;
        index index.html index.htm index.nginx-debian.html;

         location / {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            try_files $uri $uri/ /index.html;
        }

         location /api/auth {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://loadbalancer;
        }
        
        location /api/tours {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://loadbalancer;
        }
    }
}

How can I ammend my NGINX proxying to allow for direct routing via the client using the url myapp.com/tours/<some-id>?

Leave a Comment