Rendering Strategies (Pages Router)
In this chapter, we will build a simple pricing page and use different rendering strategies to render the same page.
Pricing Page
Let us create a simple pricing section with two pricing plans: "Monthly" and "Yearly".
The code for this is not super important, so feel free to copy and paste the content for PricingCard
and PricingSection
components from the below code snippets. We are storing these in the components
folder.
The pricing section.
Let us use the PricingSection
component in our PagesRouterSSR
page. This page will deal with Server Side Rendering (SSR)
with some wrapper styles.
The pricing is static at the moment. How do we show a dynamic price based on some personalization? For example, based on the user's country?
Let us create the getServerSideProps
function inside of the PagesRouterSSR
page and check for the request headers. There would be a lot of headers but the one we are interested in is x-forwarded-for
which contains the IP address of the user.
Refresh the page and you see a weird message in the console. Technically, we won't be able to get our current IP address because we are running a development server. So, how do we expose our dev server to the internet and get a real IP address? Some services and tools help us do that. For this course, we will use ngrok
ngrok
The setup for ngrok is quite simple. Just follow the instructions in their getting started guide.
Once the setup is done, with your current dev server running open another terminal and run the following command:
Then navigate to the /pages-router/ssr
router on whatever free URL you get from ngrok
and you should see a real IP address in the terminal (in the terminal where your dev server is running).
Do note, that the ngrok
free URL changes every time you run nrok
server. So, be mindful of the URL you are using.
Alright, now that we have access to the real IP address, let us use that to get the geographic location of the user.
Geolocation
We will use a free service to get the country of the user. There are various services free and paid that provide this geolocation information. But Country.is is good enough for our use case.
Back in our PagesRouterSSR
page, let us update the code to use Country.is
endpoint:
You can update the PRICES
object to suit your needs. Since we are based out of India, we will assume that the country is India (country code IN
).
Let us also display these regional prices in the PricingSection
component.
And pass the prices to the PricingSection
component.
Check the nrgrok
URL and you should see pricing customized to the country of the user.
Server Side Rendering (SSR)
So far we have still been working with a development server. Let us run the build command and host that build server on ngrok
.
Run the build command:
You should see an output like the following:
The Next.js build output would indicate the pages that are server-rendered on demand.
What does this mean? The page pages-router/ssr
will be created on each request. This means that for every user who visits this page, a new page will be created based on the IP address. This is crucial because if this page was static, the pricing would be static too.
We recommend reading the "Design A Dynamic Pricing Page" system design for a better understanding of these rendering strategies and how they would impact the user experience.
Previewing the Build Server
Let us use ngrok
to preview the build server. First, run the following command:
Then run the ngrok
command:
Check the ngrok
URL and you should see pricing customized to the country of the user. This time you are viewing the actual build server instead of the development server. Network requests would be the same as if you were running a production server.
Static Site Generation (SSG)
But can we get the same result with Static Site Generation (SSG)
? Technically yes! But the way, we do it is drastically different and would involve domain-level restructuring.
We would have to create a pricing page or landing page for each of the countries we want to support. This way we can have static pages that are customized to the country of the user. This is a pretty common practice and Netflix, for example, does it.
Let us see how we can achieve that. Create a dynamic page called [country].tsx
inside pages/pages-router
and add the following code:
Most of the logic remains the same except now we generate a new page at build time based on the countries we support. getStaticPaths
helps us identify all the paths that should be generated at build time. getStaticProps
helps us pass the data required for those pages.
Run the build command again:
You should see an output like the following:
This time you would see the pages that are pre-rendered at build time (SSG or static HTML). The only problem now is that you expect that the user will navigate to the exact page based on their country. This is a bit too much to expect from the user.
So, let us automate this process.
Geo Redirection
So, far the pages-router
index route does not have a page and will result in a 404. Let us assume that the user would always go to this route instead of the country-specific page. But we'll redirect the user from the index page to the country-specific page based on their country.
This is where we can leverage middleware
to redirect the user to the country-specific page. Create a middleware.ts
file inside the src
directory (at the same level as pages
and app
folders) and add the following code:
Run the build command again:
You should see an output like the following:
With the build server (npm start) and ngrok
server (ngrok http http://localhost:3000) running, navigate to the /pages-router
router on whatever free URL you get from ngrok
and you should be redirected to the country-specific page.
What is happening here? First up, we check if the user is coming from the index route /pages-router
. If so, we get the IP address of the user and use it to get the country of the user. Then we redirect the user to the country-specific page. All other requests that do not match this pattern will be handled by the next
middleware (a.k.a continue with the request).
The process seems repetitive. We had to create a page for each country, and then redirect the user to the country-specific page. Whereas with SSR, all of this was handled in a single step. There are advantages and disadvantages to both approaches. Choose the one that works best for your use case.
We still have another rendering strategy called Client Side Rendering (CSR)
that has its advantages and disadvantages.
Client Side Rendering (CSR)
This approach might be the simplest to understand and utilizes a client-side network request to render the prices. This approach renders a static page but gets additional data from the server after the page is rendered.
Since we are making a network request, let us use a package called SWR
to handle some of the caching stuff.
Create a pages-router/csr.tsx
file inside the pages
directory and add the following code:
We first create a simple fetcher
utility function that will fetch the data from a URL. Another function called getPrices
will be used to get the prices.
Note that we do not pass the IP address
explicitly this time. The Country.is API will handle that for us.
Next up, we create a custom hook called usePrices
that will fetch the prices from the Country.is
API. This custom hook calls the useSWR
hook with a fallbackData and revalidation options (these revalidations options help not call the API on focus or reconnect).
We will use this hook in the PagesRouterCSR
component which pretty much remains the same.
Run the build command:
You should see an output like the following:
With the build server (npm start) and ngrok
server (ngrok http http://localhost:3000) running, navigate to the /pages-router/csr
router on whatever free URL you get from ngrok
and you should see the relevant pricing though there might be a little layout shift.
This approach makes an additional network request to get the pricing though with fallback data a full static page is rendered before the network request potentially updates the pricing.
Summary
In this chapter, we have learned how to render a dynamic pricing page with different rendering strategies. There are more rendering strategies like Incremental Static Regeneration (ISR)
and Partial Prerendering (PPR)
.
ISR is just like SSG but helps you update static pages after the initial build and is ideal for something like a blog but for our use case has zero benefits.
PPR is an experimental feature that gives you the benefit of SSR and SSG. This feature is highly promising and greatly increases the user experience and performance. We will cover this feature as a bonus section once it is stable.
That is it for this chapter, in the next chapter we will look at the same rendering strategies but with the App Router
and how a lot of our code is simplified.
At this point, our code should match the code in the branch 2-rendering-pages
.