-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsuperfast-liveview.html
290 lines (275 loc) · 11.4 KB
/
superfast-liveview.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
<!DOCTYPE html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Superfast Webapps using NextJS, Vercel, LiveView and Fly.io</title>
<meta
name="description"
content="IndiePaper has a NextJS frontend which proxies requests to theLiveView backend. This means that I can easily edit and serve themarketing pages separtely and make it available even when the mainsite goes down. This post outlines the setup."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="./styles/output.css"
type="text/css"
media="screen"
/>
<script
defer
data-domain="aswinmohan.me"
data-api="/stats/api/event"
src="/stats/js/script.js"
></script>
</head>
<body>
<div class="flex items-baseline justify-between">
<p>
<a href="/">back to home</a>
</p>
<p>2022-Jan-14</p>
</div>
<div
id="job-banner"
class="border-primary-300 mt-12 border bg-gray-200 px-4 py-2"
>
<p>
I'm currently looking for fulltime/contracting oppurtunities in Elixir,
Phoenix and LiveView. If you're looking for a kickass Product focused
Elixir developer, I'm your guy. You can find my resume
<a href="./resume.pdf">here</a> and reach me at
<a href="mailto:hey@aswinmohan.me">hey@aswinmohan.me</a>.
</p>
</div>
<article>
<section>
<h1>Superfast Webapps using NextJS, Vercel, LiveView and Fly.io</h1>
<p>
Making fast web apps is hard but a tremendous
<a
href="https://www.businesswire.com/news/home/20180724005488/en/Slow-Websites-Are-Silent-Killers-for-Businesses"
>business oppurtunity</a
>. It's also the right thing to do. In this blog post, I'll explain
how <a href="https://indiepaper.me">IndiePaper</a> achieves decent
performance with NextJS, and Phoenix hosted on Vercel and Fly.io
respectively. Big thanks to Plausible.io for pointing out the
<a href="https://github.com/plausible/analytics/discussions/1593"
>way</a
>.
</p>
<p>
Making a fast web app is complex, It requires tuning a lot of moving
parts. The technology that you chose has a measurable impact on
performance. We chose these technologies because they optimize for and
care about performance.
</p>
<h3>NextJS</h3>
<p>
It's a framework based on React, geared towards static and dynamic web
apps. It has a lot of sane defaults and can get you a halfway decent
SPA experience out of the box. It has a couple of features like
optimized images, preloading routes, chunked downloads and dead code
elimination that makes the user experience insanely fast.
</p>
<h3>Vercel</h3>
<p>
They are the company behind NextJS, providing great support for
hosting and distributing NextJS apps. They deploy your static site via
their global CDN close to your users. With NextJS and Vercel people
can see sub 50ms response times for your website.
</p>
<h3>Phoenix LiveView</h3>
<p>
Phoenix is the premier web framework for Elixir. It's fast, with
microsecond response times, has an intuitive programming model and is
the best fit for making dynamic server-side apps. LiveView takes this
up one notch by making the data transfer over a persistent WebSocket
connection which gives an SPA like experience.
</p>
<h3>Fly.io</h3>
<p>
Hosting static sites closer to people using CDNs is kind of easy. But
doing the same for dynamic server-side web apps is still a hard
problem. Fly.io streamlines running your apps close to people. It
supports databases, work well with Phoenix, has library support for
global databases and is comparatively cheap. Phoenix LiveView on
Fly.io is a killer combination, and with enough regions, it's
indistinguishable from a Single Page App.
</p>
<h2>Separating Static and Dynamic</h2>
<p>
I implemented this setup, not because of the obvious performance
benefits, but because I wanted to separate static pages like the home
page, privacy policy and dynamic content from the dynamic author
dashboard. Initially, all pages including the homepage were served
from the Phoenix app itself. This meant waste of server resources,
pages couldn't be reliably cached, and every small grammatical change
polluted the main application repo and needed a server restart.
</p>
<p>
I was fine with this until I wanted a blog, and that too as a
subdirectory for SEO reasons. That's when I decided to move the static
pages to a NextJS app that can be statically generated and serve the
remaining pages from the Phoenix app. That way we get the speed of
static sites, and the interactivity of Phoenix LiveView.
</p>
</section>
<section>
<h2>Domain Setup</h2>
<ol>
<li>
Point Domain Nameservers to Vercel (optional) This makes sure that
all requests directly pass to Vercel, which improves performance.
</li>
<li>
Deploy NextJS app to Root Domain on Vercel Use the root domain
<code>https://yourdomain.com</code> for deployment, and make sure to
redirect <code>www.yourdomain.com</code> to it.
</li>
<li>
Deploy Phoenix LiveView to app subdomain on Fly.io Use fly.io and
deploy the LiveView app to subdomain `app.yourdomain.com`. Point the
subdomain to the fly.io IP address and generate a certificate. Make
sure to set the <code>PHX_HOST</code> variable as the root domain
<code>https://yourdomain.com</code>.
</li>
</ol>
</section>
<section>
<h2>Using Vercel as a Reverse Proxy</h2>
<p>
We want all our requests to first go to Vercel and need Vercel to
forward all requests it cannot fulfil to our LiveView app. NextJS has
a handy feature called
<a
href="https://nextjs.org/docs/migrating/incremental-adoption#rewrites"
>Rewrite Fallback</a
>, that enable us to do just that.
</p>
<p>
When a request hits our NextJS app it checks if the NextJS app has
that route. If it has, then it fulfils that request. If there is no
corresponding route, rather than showing a 404 page, Vercel then
proxies the requests to our LiveView app hosted on our
<code>app</code>
subdomain, without changing the URL. Put this config in
<code>next.config.js</code>.
</p>
<pre>
module.exports = {
async rewrites() {
return {
// After checking all Next.js pages (including dynamic routes)
// and static files we proxy any other requests
fallback: [
{
source: '/:path*',
destination: `https://app.example.com/:path*`,
},
],
}
}
}
</pre>
</section>
<section>
<h2>Websockets and Cookies</h2>
<p>
If you were using vanilla Phoenix apps, this was only necessary. But
since we are using LiveView we need some additional setup.
</p>
<h3>Websockets</h3>
<p>
By default the LiveView WebSocket will be started in
<code>wss://yourdomain.com/live</code>. But Vercel does not support
proxying WebSockets. We need to directly connect to our app running on
the `app` subdomain. Put this in your <code>app.js</code> file in
LiveView. This sets the WebSocket connection on <code>app</code> when
deployed and localhost when running locally.
</p>
<pre>
const host = window.location.host;
let liveHost = "";
if (host === "yourdomain.com") {
liveHost = "wss://app.yourdomain.com";
}
const socketHost = `${liveHost}/live`
let liveSocket = new LiveSocket(socketHost, Socket, {
...
}
</pre>
<h3>Cookies</h3>
<p>
Phoenix stores session information in cookies, which are sent along
with every response. Since we are using the same domain as defined by
<code>PHX_HOST</code>, cookies are sent correctly with the HTTP
request. But cookies are not sent with the Websocket connection
because we are hosting it on the <code>app</code> subdomain. By
default, cookies are not shared between subdomains. Configure cookies
to be shared across subdomains on your <code>endpoint.ex</code> file.
</p>
<pre>
@session_options [
...
domain: ".yourdomain.com"
]
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
</pre>
<p>
Remember to put the dot. It makes sure the cookies with your session
information is shared.
</p>
<h3>Check Origin</h3>
<p>
In <code>runtime.exs</code> file be sure to add both your domains so
your requests are not rejected.
</p>
<pre>
config :your_app, YourAppWeb.Endpoint,
url: [host: host, port: 443],
check_origin: [
"https://#{host}",
"https://app.#{host}"
],
</pre>
</section>
<section>
<h2>Results</h2>
<p>
IndiePaper follows this architecture. You can check it out at
<a href="https://indiepaper.me">https://indiepaper.me</a>
</p>
<p>
The root domain points to a configured
<a href="https://github.com/timlrx/tailwind-nextjs-starter-blog"
>https://github.com/timlrx/tailwind-nextjs-starter-blog</a
>
blog hosted on Vercel. It handles all the traffic from around the
world and serves the blog on
<a href="https://indiepaper.me/blog">https://indiepaper.me/blog</a>. I
can edit blog posts add and remove pages, all without touching the
main phoenix app.
</p>
<p>
Once the author signs up, the author is taken to
<a href="https://indiepaper.me/dashboard"
>https://indiepaper.me/dashboard</a
>. It is hosted on <code>app.indiepaper.me</code> proxied through
Vercel. Once the author reaches the backend, LiveView takes over. No
further HTTP requests are made as the navigation takes place through
<a
href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Router.html#live_session/3"
>live_session</a
>. The WebSocket directly connects to Fly.io without going through
Vercel making the app navigation smooth and fast.
</p>
<p>
Seperating out your Static and Dynamic content is neccesary for SEO
and marketing reasons. Splitting and hosting your app this way enables
you to have a great user experience while having minimal impact on
your developer experience. Suggestions are always welcome.
</p>
</section>
</article>
</body>
</html>