holman / ama

Ask @holman anything!
731 stars 277 forks source link

Reverse AMA: how do you reliably host a marketing site and a single page app? #836

Closed holman closed 6 years ago

holman commented 6 years ago

Hi! Thought this would be an interesting place for me to ask questions to y'all.

So I'm mulling over ways to handle during.com when it ships (which is hopefully soonish! There's almost 9,000 of you on the beta list, so lol, I'll need to figure out a way to run through all of that safely too, but that's another issue entirely).

There's two aspects I'm wondering about here when I ultimately deploy: the marketing site ("hey look at these reasons why you should give me money!") and the web app itself ("hey look a neat calendar!")

The marketing site will be either on Pages or a CDN, naturally. When I launch I'm sure During will be a top ten site on the internet for that day, and certainly you don't want the main page to go down (that's what your backend is for!)

The app is a single-ish page app.

Ideally I'd like to host both of them on the bare during.com domain. I don't like having to put the app itself on a subdomain (app.) or a directory (/app), which is one approach I've seen done. Otherwise, you could just put a redirect to the marketing page if you're not logged in in the SPA itself, but I don't want to load any of the app bundle on the marketing page (even if it's gone through proper code splitting; mostly just want to not have any extra overhead for the marketing site).

@maddox had a wild idea about actually utilizing the difference of a www. subdomain, and maybe use https://during.com for the app and https://www.during.com for the marketing site (and if you have a logged-in cookie on the marketing site, that's when you redirect to the app). I feel like that could feasibly also be confusing, or possibly even frowned upon by a Google or something similar, but lol it's kinda an interesting idea.

I guess I could also do some sort of routing at the router-level, or otherwise write something high-level to choose between the two depending on the existence of a session cookie. I'd ideally like to avoid putting any moving parts that could feasibly experience downtime in front of a simple static site, so that's unfortunate, but maybe there's something in the AWS suite of tools that I don't know about that might be able to pretty easily handle this?

Anyway, thought I'd open this to the crowd since it's kinda hard to google. How would you tackle it? Or how has your company tackled this in the past?

Cheers! ✌️

searls commented 6 years ago

I don't know man. I'd mount a subpath to be honest, as the subdomain sprawl of many apps irks me.

One thing I'd be wary of is having marketing pages on a www subdomain and app on the root domain. Say you have a snazzy and short URL at www.during.com/pay-zach and someone excitedly texts their friend hand-types it during.com/pay-zach. They're probably going to see an unhelpful 500 error from the app and Zach won't get paid

maddox commented 6 years ago

One thing I'd be wary of is having marketing pages on a www subdomain and app on the root domain. Say you have a snazzy and short URL at www.during.com/pay-zach and someone excitedly texts their friend hand-types it during.com/pay-zach. They're probably going to see an unhelpful 500 error and Zach won't get paid

lol, this is enough to 🔥 this.

anaisbetts commented 6 years ago

If your site is a React app, just go the PWA route and put the entire site on a CDN, and make your marketing page just the logged-out experience. This means your logged-in site is Fast As Fuck too and CDNd, fuck a color Mustache*

(*this is a GitHub Inc Joke, it's a long story)

daveoflynn commented 6 years ago

I don’t quite see how using bare vs www to discriminate between application and marketing site is any better than app vs www - you’re still discriminating via DNS with the added complication of breaking existing conventions.

The two approaches I see most often are app.during.com and during.net. I’ll be fascinated to see if there are better approaches.

gkoberger commented 6 years ago

We use fly.io for this. There's two relevant things it does:

  1. Route users based on the existence of a cookie. So, logged in users could see a logged in page, while logged out users see something from a CDN.
  2. Route subfolders to different applications, so you could have /blog go to a WordPress instance and /whatever go to another service

(cc @mrkurt)

danielcompton commented 6 years ago

One thing to consider with having your app on the same domain as the marketing site is that any XSS on the marketing site (say via comments on posts) gives you cookies on the app site. There are mitigations of course, but this is still a risk.

holman commented 6 years ago

If your site is a React app, just go the PWA route and put the entire site on a CDN

I suppose that's not too terrible of an approach. I kinda liked it from a high-level of having a separate repo (or in my case, a separate dir in my ✨monorepo✨) of just the marketing site, so new devs can work on it without having to learn all the code splitting needs of the main app. I'm also not sure how much overhead the need to codesplit a simple static marketing page would be (just haven't looked into it enough myself).

latentflip commented 6 years ago

To add to the things that irk me list: it’s slightly maddening if I have signed up to try an app but then want to go back read the marketing site (pricing/features/etc) but I can’t because it sees I’m logged in and auto redirects me to the app, rather than letting me view the marketing site.

ryantology commented 6 years ago

What / how do you plan to manage the marketing / content site? If they are static files I would probably just smash them into the app. If not, I would have your webserver (nginx?) check the auth (JWT?) and then route to the app or logged out. I like having the app live at /dashboard or something similar.

I often have projects that need to have the app live at / and the marketing managed through wordpress (there is a team) and have that hosted at /blog or any other sub folder. Because I don't like managing wordpress we use a reverse proxy and have it managed by flywheel.

JonAbrams commented 6 years ago

without having to learn all the code splitting needs of the main app

Use Next.js and you won't have to worry about this. Having a unified marketing + app in one project is really nice.

holman commented 6 years ago

One thing to consider with having your app on the same domain as the marketing site is that any XSS on the marketing site (say via comments on posts)

That's a really good point. I won't have comments and stuff, but yeahhhh.

We use fly.io for this.

Interesting. Will look into them more.

To add to the things that irk me list: it’s slightly maddening if I have signed up to try an app but then want to go back read the marketing site (pricing/features/etc) but I can’t because it sees I’m logged in and auto redirects me to the app, rather than letting me view the marketing site.

Hah, yeah same. I always liked that /home was always a thing while I was at GitHub (and they're still doing it). Too many people just ignore that entirely. :\

mrkurt commented 6 years ago

Oh yes, we (fly.io) built a whole company to solve this problem (well not just this problem, but it's one of the more irksome things we ran into at previous companies). We do this:

In summary @gkoberger is a genius.

holman commented 6 years ago

What / how do you plan to manage the marketing / content site? If they are static files I would probably just smash them into the app.

Yup, just a static site. Would have liked to have a completely separate, standalone project for my marketing site, but it might make sense to do the ol' smash-them-into-the-app, hah.

knksmith57 commented 6 years ago

I'll also caution against clobbering the apex domain with both marketing + app for 2 reasons (and happy to elaborate more if you care to hear):

You can always add routing rules via reverse proxy or robust CDN later and keep a 302 redirect on it for now

daveoflynn commented 6 years ago

I’m certainly interested in more detail @knksmith57.

holman commented 6 years ago

As far as I can tell, code splitting doesn't really make any sense, because there's not a particularly easy way to code split in a create-react-app app without having to load React and the rest of its heavy world along with it. Not the best way to start out if you want to build a fast site.

Guess I'll have to investigate a subdomain or a directory approach? Ugh.

bmoeskau commented 6 years ago

and if you have a logged-in cookie on the marketing site, that's when you redirect to the app

Please, I beg you, don’t do this. Seriously irritating when trying to refer back to the marketing site for pricing etc. Huge pet peeve.

holman commented 6 years ago

I'd certainly make /pricing and other assorted pages available if you navigate to them directly, but I don't think I'd ever not have during.com redirect to the app if you have a session cookie. Particularly for an app that you use frequently, I find it a huge pain to have to make that extra "go to the app" click.

mbyczkowski commented 6 years ago

I find it a huge pain to have to make that extra "go to the app" click.

Yeah, I really like the way github.com works for logged-in users, and I can still check any other page (AFAICT), including /pricing. On the other hand if I had content-heavy website that would take me to some dashboard if I was logged in, I could see myself being annoyed.

If during.com is the former, it definitely makes sense to me.

edorsey commented 6 years ago

You might be able to deploy the app to the catch all route on S3/static site host.

That way, all of your static content would take priority, then if it 404'd, it would load the app.

Unfortunately, your app would need to know about that static content so it handled those links properly.

mbrevda commented 6 years ago

As far as I can tell, code splitting doesn't really make any sense, because there's not a particularly easy way to code split in a create-react-app app without having to load React and the rest of its heavy world along with it

Our approach was to SSR (server side render) the static pages as part of the build process. This ensured that if someone landed on our marketing pages the content would appear extremely fast, as all the browser needed was the html + css. Once the browser finished parsing/painting it continued and downloaded/executed the js "app". This way, if the user used decided to navigate to a different page or to sign up all the code SPA was already in place!

Looping back, if the user was on a more dynamic page and navigated to a static page we could still render instantly (without a server call) as ultimately all the code for the static pages were still part of the SPA (albeit properly code split, so maybe we had to load the chunk. See bellow).

As the cherry on top, we use service workers to ensure that most of the code split chunks (and associated assets) were downloaded in the background when the user was idle, allowing us to ship a really small bundle to start but still giving the user an almost zero latency experience.

anaisbetts commented 6 years ago

SSR has a lot of frustrating problems once you get into production tbh. At the end of the day, SSR is really just equivalent to, "React Site but I get to pre-can a JSON call as part of the initial request". This sounds Cool™ because it saves a call, but it comes with a number of disadvantages:

  1. CDNing your site now becomes way more obnoxious, because part of your app is now dynamically generated
  2. Managing site changes sucks ass, if someone is on v1 of your app, you deploy v2, your load balancer is now in the game of making sure old sessions get v1 of the app. Granted, PWAs can hit this problem too, but it's harder to get a skewed version sitch
  3. Same thing for a load-balancer scenario, deploys aren't instantaneous - this means that for a small amount of time, 1/2 your servers are gonna be serving v1 and half will serve v2, which gets Weird for SSR
  4. Now every component has two separate paths through it, debugging failed server-side JSON calls sucks ass because there's no DevTools console
tpatel commented 6 years ago

From AWS, the Application Load Balancer seems to be what you're looking for:

Support for path-based routing. You can configure rules for your listener that forward requests based on the URL in the request. This enables you to structure your application as smaller services, and route requests to the correct service based on the content of the URL. Source: http://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html

assimovt commented 6 years ago

Zach 👋

Glad you're pushing out During soon :) As for the setup, we've had a similar dilemma at Dockbit. We wanted to keep our marketing site (Rails) separate from the app (Ember), but have them under the same dockbit.com domain.

Marketing site would usually grow into a bunch of other things (analytics, A/B, or even 3rd party services) that the app won't ever need, so definitely keep them separate.

We're also sharing a cookie between these two apps, but still, give an option for the user to return to Home and check the pricing/features/etc. We show "Back to Dockbit" button if you're signed in.

To get all that working, we have our own Nginx proxy that routes known (whitelisted) URLs to marketing site and falls back to the app. Some of these things we've shared in this blog post: https://blog.dockbit.com/our-path-to-ember-bd6ebbf0b94a

I guess in your case you'd need to configure S3 redirects, but I am not sure how flexible they are, so you'll probably end up having a sub-url/subdomain, like an "app" maybe?

Hit me up if you'd any help with it!

paulrose commented 6 years ago

Meh, I really dislike app and marketing on one domain, it tends to mean that if there’s ever a need to visit the marketing site you need to logout of the app. I’m a fan of app. or /app for the split

Had to log out of Dropbox to check out new branding, couldn’t log back in as didn’t have phone with me for 2FA. Cue no web Dropbox because no split.

assimovt commented 6 years ago

Meh, I really dislike app and marketing on one domain, it tends to mean that if there’s ever a need to visit the marketing site you need to logout of the app. I’m a fan of app. or /app for the split

@paulrose Not necessarily, you could still be logged in and navigate between the two. Just need to handle how Marketing site looks like when there is a login cookie, for instance showing a button "Back to app" as we do. I've seen some sites showing personalized Marketing site when a user is logged in, which makes it even cooler.

mbrevda commented 6 years ago

SSR has a lot of frustrating problems once you get into production tbh

I don't see a need to dynamically create a marketing page. Simply generate the marketing html pages during your build step and pop the whole thing onto a CDN.

holman commented 6 years ago

Some good #content in here; really appreciate all this!

From AWS, the Application Load Balancer seems to be what you're looking for:

That's all path-based though, no? That helps for things like /pricing and /about and such, but I'm still stuck sorting out the main root path case, which is overloaded with two different services.

paul commented 6 years ago

I don't know how it looks from a SEO standpoint, but you can have your "front" marketing page live at /about, and if the app gets a request to / without credentials, then redirect. On a side project of mine, I have the marketing stuff all at /brochure.

knksmith57 commented 6 years ago

fwiw, having done all this stuff in AWS (multiple domains, static sites w/S3, custom routing + redirects, route53 for DNS management (which is a disappointing, artificially imposed requirement for doing this in AWS), API GW => lambda for dynamic HTTP request handling)...

If you aren't married to AWS, you might consider a brief look at Firebase hosting: https://firebase.google.com/docs/hosting/.

2 years ago I would never have recommended it for your use case, and it still might fall short for some things; however, it does come batteries included for many of your requirements:

Specifically for this thread having app behavior that requires sessions and marketing content that does not (necessarily) the first-class session and authentication features could be a really big time saver + DX win.

holman commented 6 years ago

Thanks for all the help and insight on this, y'all! I think I'm going to go with a redirect => subdomain'd approach (so marketing will always be visible/hosted on the main domain, and the app somewhere else). Seemed the most sane choice for now.

✌️