I have partial answers to my own question.
For issue 1:
The issue is my own fault. assetPrefix worked with SSR but not for SSG because I didn't pass in environmental variables properly. In my situation, we have 2 different CDN URLs for production and staging. So I have something like the following in next.config.js. Because MY_ENV is passed in from PM2, which starts my app, it is guaranteed that MY_ENV will always be available when Next.js needs to access next.config.js.
// next.config.js
const isProd = process.env.MY_ENV === 'production';
const isStaging = process.env.MY_ENV === 'staging';
const isDevelopment = process.env.MY_ENV === 'development';
if (isProd) {
assetPrefix = 'https://mycdn.cloudfront.net/';
} else if (isStaging) {
assetPrefix = 'https://mycdn.cloudfront.net/staging';
}
However, when I run next build for static pages, the build step doesn't use PM2 thus MY_ENV is not available. To make it work, I need to run the build twice with different variables.
"build": "npm-run-all --parallel build:production build:staging",
"build:production": "MY_ENV=production next build",
"build:staging": "MY_ENV=staging next build",
For issue 2:
If I'm able to pre-generate all static pages. I can just put everything on CDN and they will work.
In my situation, ISR is a better fit. The way I get ISR to work is to ask the server to return the HTML for every request instead of hosting on the CDN. Since all other assets are hosted on the CDN, the performance is still pretty good and this solution works out well for my situation.
If you are like me who struggled a bit on this issue, I hope my answer helps you out.