netlify / angular-runtime

The Angular Runtime allows Angular to run on Netlify with zero configuration
https://docs.netlify.com/integrations/frameworks/angular/
MIT License
19 stars 11 forks source link

Server.ts changes are not being process after deploying to nelify but works fine locally #135

Closed mrrohit1 closed 3 months ago

mrrohit1 commented 4 months ago

Describe the bug Update my server.ts file to manage 301 redirect and added sanitization for json in final souuce code but both changes are not working, i can see its working locally but after deploying to netlify its not working

To Reproduce Steps to reproduce the behavior:

  1. Make 301 redirects in server.ts file

    
    function app(): express.Express {
    const server = express();
    const serverDistFolder = dirname(fileURLToPath(import.meta.url));
    const browserDistFolder = resolve(serverDistFolder, '../browser');
    const indexHtml = join(serverDistFolder, 'index.server.html');
    
    const commonEngine = new CommonEngine();
    
    server.set('view engine', 'html');
    server.set('views', browserDistFolder);
    
    server.get('*.*', express.static(browserDistFolder, {
    maxAge: '1y'
    }));
    
    server.get('*', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;
    
    const redirectUrl = redirectMap[req.url];
    if (redirectUrl) {
      res.redirect(301, redirectUrl);
    }else{
    
    commonEngine
    .render({
      bootstrap,
      documentFilePath: indexHtml,
      url: `${protocol}://${headers.host}${originalUrl}`,
      publicPath: browserDistFolder,
      providers: [
        { provide: APP_BASE_HREF, useValue: baseUrl },
      ],
    })
    .then((html) => {
    
      // Extract the state
      const stateRegex = /<script id="ng-state" type="application\/json">(.*?)<\/script>/;
      const match = html.match(stateRegex);
      if (match && match[1]) {
        const state = JSON.parse(match[1]);  
        const sanitizedState = sanitizeState(state);        
        const serializedState = JSON.stringify(sanitizedState);
        // Replace the original state with the sanitized state
        html = html.replace(stateRegex, `<script id="ng-state" type="application/json">${serializedState}</script>`);
      }
    
      res.send(html);
    
    })
    .catch((err) => next(err));
    }
    
    });
    
    return server;
    }

function run(): void { const port = process.env['PORT'] || 4000;

const server = app(); server.listen(port, () => { console.log(Node Express server listening on http://localhost:${port}); }); }

run();


redirectMap: 

export const redirectMap: { [key: string]: string } = { 'old url 1' : 'new url 1', 'old url 2' : 'new url 2', 'old url 3' : 'new url 3', // other urls }

sanitize-state :

export function sanitizeState(state: any): any { const sanitizedState = JSON.parse(JSON.stringify(state));

Object.keys(sanitizedState).forEach(key => {
  if (sanitizedState[key]?.u) {
    delete sanitizedState[key].u;
  }
});

return sanitizedState;

}


**Expected behavior**
after deployment redirect should work but its not working and same for sanitize state

**Versions**
- Angular.js: 18

**If you're using the CLI to build**
N/A

**If you're using file-based installation**

[build] command = "npm run build" publish = "dist/dapps/browser"

[[plugins]] package="@netlify/angular-runtime"

[[redirects]] from = "/*" to = "/index.html" status = 200



reason for using redirect in server.ts because redirect is not supported in SSR and i have to redirect more than 3000 urls 
Skn0tt commented 3 months ago

Hi @mrrohit1! server.ts is a file auto-scaffolded by Angular and it can be used to run your Angular application e.g. on a VM. It doesn't work on Netlify, though. That also gives some benefits, for example it allows us to serve static assets from a CDN instead of having to use SSR for that.

For your usecase, I'd recommend you to use an Edge Function. Here's a draft for how it could look:

// .netlify/edge-functions/redirect-and-sanitize
import { 
  HTMLRewriter 
} from 'https://ghuc.cc/worker-tools/html-rewriter/index.ts'

export default (request, context) => {
    const redirectUrl = redirectMap[request.url];
    if (redirectUrl) {
      return Response.redirect(redirectUrl, 301);
    }

   const response = await context.next()
   if (!response.headers.get("content-type").startsWith("text/html")) {
      return response
   }

  return new HTMLRewriter()
    .on("#ng-state", {
        element(element) {
            const state = JSON.parse(element.innerHTML);  
            const sanitizedState = sanitizeState(state);        
            element.innerHTML = JSON.stringify(sanitizedState);
        },
    })
    .transform(response)
}

export const config = {
   path: "/*"
}

You'll notice that I replaced your Regex-based approach with HTMLRewriter. That allows us to perform the state sanitisation without blocking the response stream, reducing memory load and latency. Using a Regex should also work fine, though.

Let us know if that helps! Sorry for the confusion with server.ts, we'll add a note the README about it.

yehor-akunishnikov commented 3 months ago

Hi, @Skn0tt. It seems to be impossible to use custom edge functions in case of SSR. In that case, there is a default edge function called 'Angular SSR', that handles server side rendering and has '/*' as a path. In result, that default edge function blocks others from excecution.

Here is my post on the support forum regarding that issue: https://answers.netlify.com/t/netlify-redirects-to-api-server-not-working-on-angular-ssr-website/119988/5. In my case, I wanted to use a custom edge function as a proxy.

Is there any way to make custom edge functions work with SSR, for example, by excluding certain paths?

Skn0tt commented 3 months ago

cc @serhalp

serhalp commented 3 months ago

@yehor-akunishnikov As my colleague mentioned in reply to your post, I don't believe that's possible currently. However, you should be able to use a custom Netlify Function that runs at the origin, instead of a Netlify Edge Function that runs at the edge. Is that an acceptable workaround for your use case?

yehor-akunishnikov commented 3 months ago

@serhalp I'll give it a try, thanks.

daroczig commented 2 months ago

@serhalp thanks for the above! could you please point me how to set up a Netlify Function to run at the origin? I was not able to find that trigger. would be great if the fn call could be tied to a path prefix as well :pray:

serhalp commented 2 months ago

@daroczig I could've been clearer! What I meant was that Netlify Functions run at the origin and Netlify Edge Functions run at the edge. So the suggestion here was to use Netlify Functions: https://docs.netlify.com/functions/overview/. That page should have everything you need!

yehor-akunishnikov commented 2 months ago

@serhalp thanks for the above! could you please point me how to set up a Netlify Function to run at the origin? I was not able to find that trigger. would be great if the fn call could be tied to a path prefix as well :pray:

@daroczig, I'll just share my own experience. I tried to use Netlify functions to solve the problem I described earlier. But I ran into the same problem as with the edge functions. In the end, I just gave up and used a VPS instead of Netlify. You may be able to achieve a better result, but for me it's too much trouble for such an obvious task, so it's not worth it.

daroczig commented 2 months ago

@serhalp I went through those pages and tried deploying a Netlify Function with a custom path config, but the Angular SSR Edge Function still seems to take priority. Do you have any pointers on what I am missing?

FTR this is what I tried as a POC:

import type { Config, Context } from "@netlify/functions";

export default async (request: Request, context: Context) => {
  console.log(url);
  return new URL("https://reqres.in/api/users?page=2", request.url);
};

export const config: Config = {
  path: "/foobar/*",
};