#SSR - On refresh my application show case the / route very quickly and than shows the actual route

45 messages · Page 1 of 1 (latest)

sweet dust
#

I'm hitting a wall here, so would like to know if someone has any clue for me where to look for debugging this issue (angular ssr)

My application works perfect if I serve it using "@angular-devkit/build-angular:dev-server:ssr-dev-server". When I build my application (prod or dev config) and serve it using node dist/autofeit-frontend/server/server.mjs it works fine too. When I build & deploy my code, I'm facing this weird issue: on refresh, doesn't matter what route there's flickering of the '' route. To make it more clear: I am on /something and I refresh, I see first the component that is at / route for couple of ms and than it goes to /something.

I'm using the latest version of angular and node.js. Any clue where to look on how to debug this? I've already spitted through my deployment script (see underneath), but cannot find any issue with it.

sweet dust
#

Build steps

steps:       
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Angular CLI
        run: npm install -g @angular/cli

      - name: Build Angular app with SSR
        run: ng build --configuration=production

      - name: Create deployment package
        run: |
          mkdir -p deploy-package
          cp -r dist/* deploy-package/
          cp package.json deploy-package/
          cp package-lock.json deploy-package/

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: deploy-package/**/*
#

deploy steps

steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-artifacts
          path: ./deploy

      - name: Stop existing SSR server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 22
          script: |
            pm2 stop autofeit-fe-dev || true
            pm2 delete autofeit-fe-dev || true

      - name: Remove old build from server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 22
          script: rm -rf /var/www/autofeit-dev/html/*

      - name: Deploy new build to server
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          port: 22
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          source: "./deploy/*"
          target: "/var/www/autofeit-dev/html/"
          strip_components: 1

      - name: Install production dependencies and start SSR server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 22
          script: |
            cd /var/www/autofeit-dev/html
            npm ci --only=production
            PORT=4001 pm2 start autofeit-frontend/server/server.mjs --name autofeit-fe-dev

      - name: Reload Nginx
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 22
          script: |
            sudo nginx -s reload
humble pivot
#

To make it more clear: I am on /something and I refresh, I see first the component that is at / route for couple of ms and than it goes to /something.

As this must a direct by router (vs 301), otherwise it wouldn't know /something.

Are you using OAuth, which may be influenced by the difference between server.mjs/dev-server -VS- nginx.

sweet dust
# humble pivot > To make it more clear: I am on /something and I refresh, I see first the compo...

I'm not using any form of authentication. Here's an example of the route

  {
    path: '',
    loadComponent: () => import('./features/landing/landing.component').then(m => m.LandingComponent),
    title: 'AutoFeit - Check het kenteken check',
  },    {
    path: 'blog',
    loadComponent: () =>
      import('./features/blog/components/blog-list/blog-list.component').then(
        (m) => m.BlogListComponent
      ),
    title: "AutoFeit Blog - Alles over Auto's en Kentekenchecks",
    data: {
      meta: {
        description:
          "Lees de laatste nieuws, tips en inzichten over auto's, kentekenchecks en de Nederlandse automarkt op de AutoFeit blog.",
        keywords:
          'autoblog, kentekencheck, auto nieuws, kooptips, APK, automarkt Nederland',
      },
    },
  },

and

  {
    path: '',
    renderMode: RenderMode.Prerender,
  },
  {
    path: 'blog',
    renderMode: RenderMode.Server,
  },
humble pivot
#

As mentioned, the redirect back to "/" is done by angular client side (not nginx).

  • Unable to load chunk will also may have this sympton "redirect back to "/"".
  • Also a potential difference between dev-server and nginx
    I assume you checked the browser network log? Any clue?
#

nodejs needs to access /browser via some relative path to get the chunk, is /autofeit-frontend1 the complete /dist` that has both the server and browser directory

sweet dust
#

If it were the difference between dev-server and nginx, what approach would you take?

frosty carbon
#

Please show your nginx config

humble pivot
#

If you want to have look at it it is hosted at website and you can see the issue if you repeatedly refresh at the blog.

Hmm. Seems like fouc flickering (Although this cannot explain why dev-server work and prod doesn't, just forget this first)

blog is child route of landing /features/landing/landing.component

Do you have logic like this <router-link (activate)="hideLandingPage()">

frosty carbon
#

The problem is likely that nginx is configured to always fall back to index.html if the page is not found. This is incorrect for Angular with SSR/SSG. It must fall back to index-csr.html

#

index.html is the prerendered HTML for the root page. index-csr.html is the skeleton needed for client-side rendered routes

#

Although in this case you are also doing SSR (not just SSR), so nginx needs to fall back to your backend server when it can't find a prerendered page.

sweet dust
#

I don't have quick access to the nginx config right now, but I'll update later on. Thanks already, at least know where to look.

I think you might be right as this project was originially created/hosted without SSR and it was later on added

frosty carbon
#

The correct nginx reverse proxy configuration for a SSG/SSR app in Angular is not trivial at all

#

Because nginx' default behavior is adversarial to this situation, such as redirecting /foo to /foo/, which is incorrect for Angular.

humble pivot
#

The /blog doc"index.html" has critical html/css from the Landing page, according to @frosty carbon , is a sympton for using wrong index

#

critical html will surely load even before angular is loaded

frosty carbon
#

This makes sure that proper Angular URLs are used (redirects are set up if they are wrong), so /foo/ and /foo/index.html redirect to /foo.
It will then try to serve any assets or prerendered HTML from SSG through Nginx. If the URL points at neither, it will defer to the Angular Node.js server. This will either try to render on the server for SSR routes or serve index-csr.html.

sweet dust
#
server {
    server_name  beta.autofeit.nl;
    port_in_redirect off;

    gzip_static  on;

    location = /index.html {
        return 301 /;
    }

    location ~ ^(?<no_slash>.+)/(?:index.html)?$ {
        return 301 $no_slash;
    }

    location ~* .(?:css|js|svg|woff2|png|jpg|jpeg|webp|ico|ttf|otf|eot|mp4|mp3)$ {
        expires 1y;
        add_header Cache-Control "public";
        root /var/www/autofeit-dev/html/autofeit-frontend/browser;
    }

    location @ssr {
        proxy_pass http://localhost:4001;
    }

    location / {
        root /var/www/autofeit-dev/html/autofeit-frontend/browser;
        try_files $uri /index.html @ssr;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/beta.autofeit.nl/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/beta.autofeit.nl/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = beta.autofeit.nl) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  beta.autofeit.nl;
    return 404; # managed by Certbot


}
frosty carbon
#

try_files $uri /index.html @ssr; This is the issue. This says "look at the file the URL points to, otherwise try /index.html, otherwise try the SSR server". But /index.html always exists and it is your prerendered home page. That means your SSR server is never hit and Nginx will always just return /index.html for any SSR or CSR route.

#

Look at the nginx config I posted, specifically line 31

sweet dust
#

I've tried to make some changes to follow your exact config, but I cannot figure it out. Isn't line 31 the same as yours?

server {
    server_name  beta.autofeit.nl;
    port_in_redirect off;

    gzip_static  on;

    location = /index.html {
        return 301 /;
    }

    location ~ ^(?<no_slash>.+)/(?:index.html)?$ {
        return 301 $no_slash;
    }

    location ~* .(?:css|js|svg|woff2|png|jpg|jpeg|webp|ico|ttf|otf|eot|mp4|mp3)$ {
        expires 1y;
        add_header Cache-Control "public";
        root /usr/share/nginx/html;
    }

    location @ssr {
        proxy_pass http://localhost:4001;
    }

    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/index.html @ssr;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/beta.autofeit.nl/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/beta.autofeit.nl/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = beta.autofeit.nl) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  beta.autofeit.nl;
    return 404; # managed by Certbot


}
#

I've changed to root to match yours aswell, but end up with this

#

If I change it back to my originial root and replace it to index.csr.html, the main page loads but all the routes are not working

frosty carbon
#

You changed the root param for location /, don't do that

#

Keep the rest the same

#

You don't want index.csr.html if you are doing SSR, that will be handled by the SSR server.
If you were not doing SSR you would replace try_files $uri $uri/index.html @ssr; with try_files $uri $uri/index.html /index-csr.html

sweet dust
frosty carbon
#

No. It has try_files $uri /index.html @ssr; instead of try_files $uri $uri/index.html @ssr;

sweet dust
#

I've applied the change, but the routes seem not to work. You can see this strange behaviour on beta.autofeit.nl

server {
    server_name  beta.autofeit.nl;
    port_in_redirect off;

    gzip_static  on;

    location = /index.csr.html {
        return 301 /;
    }

    location ~ ^(?<no_slash>.+)/(?:index.csr.html)?$ {
        return 301 $no_slash;
    }

    location ~* .(?:css|js|svg|woff2|png|jpg|jpeg|webp|ico|ttf|otf|eot|mp4|mp3)$ {
        expires 1y;
        add_header Cache-Control "public";
        root /usr/share/nginx/html;
    }

    location @ssr {
        proxy_pass http://localhost:4001;
    }

    location / {
        root /var/www/autofeit-dev/html/autofeit-frontend/browser;
        try_files $uri $uri/index.html @ssr;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/beta.autofeit.nl/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/beta.autofeit.nl/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = beta.autofeit.nl) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  beta.autofeit.nl;
    return 404; # managed by Certbot


}
frosty carbon
#
location = /index.csr.html {
        return 301 /;
    }

This makes no sense. Change it to /index.html

#
location ~ ^(?<no_slash>.+)/(?:index.csr.html)?$ {
        return 301 $no_slash;
    }

Same here

#

https://beta.autofeit.nl/license-check returns 502 bad gateway

#

likely this means your SSR server (which was not used until now) does not actually work

#

Nginx reports this as 502 Bad Gateway

sweet dust
#

Hmm changed, but now seeing a cloudflare issue on the /blog route

humble pivot
#
proxy_pass http://localhost:4001

usually is upstream name instead of localhost? nginx has to define dns resolver if using hostname. try 127.0.0.1
or define upstream

#

upstream node_js {
  server 127.0.0.1:4001;
  keepalive 128;
}
sweet dust
# humble pivot ``` proxy_pass http://localhost:4001 ``` usually is `upstream` name instead of l...

I tried changing the proxy pass, no luck. Added the upstream, no change

server {
    server_name  beta.autofeit.nl;
    port_in_redirect off;

    gzip_static  on;

    location = /index.html {
        return 301 /;
    }

    location ~ ^(?<no_slash>.+)/(?:index.html)?$ {
        return 301 $no_slash;
    }

    location ~* .(?:css|js|svg|woff2|png|jpg|jpeg|webp|ico|ttf|otf|eot|mp4|mp3)$ {
        expires 1y;
        add_header Cache-Control "public";
        root /usr/share/nginx/html;
    }

    location @ssr {
        proxy_pass http://127.0.0.1:4001;
    }

    location / {
        root /var/www/autofeit-dev/html/autofeit-frontend/browser;
        try_files $uri $uri/index.html @ssr;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/beta.autofeit.nl/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/beta.autofeit.nl/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

upstream node_js {
   server 127.0.0.1:4001;
   keepalive 128;
}

server {
    if ($host = beta.autofeit.nl) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  beta.autofeit.nl;
    return 404; # managed by Certbot


}
humble pivot
#
location @ssr {
  proxy_pass http://node_js
}

will do. no need the port anymore as it is inside upstream

frosty carbon
#

Also, make sure the server is actually running, you can use curl when connected over SSH

sweet dust
#

Thank you guys so much! Quite new to this but it is resolved

You were right with the server not running. There was some issues with PM2 and not starting up the server with the port.