Real Estate Listing with Next.js: An Unexpected Journey

Gigiz
Casavo
Published in
6 min readAug 26, 2020

Next.js is a React.js based framework that implements the Server-Side Rendering (SSR) technique. At the moment the latest version released is 9.5 with a lot of new features. Zero configurations, TypeScript support, automatic compilation and bundling, code splitting, high performance and, above all, what our product team likes most, the ability to better manage SEO.

For all of these reasons we have chosen it as the frontend technology for our real estate listing.

At the same time, a backend for frontend (Elixir + GraphQL) was also developed, in order to provide the listings data to our web application. But that’s another story…

This is not going to be the usual article on “how to setup an application with Next.js”, the documentation on the official website is enough. I would like to focus on aspects that, although might look trivial now, took me a lot of time in order to master.

1. SWR
Let’s start with the choice of the main library, SWR that is a React Hooks library for remote data fetching also developed by Vercel.

const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)

fetcher is any function that returns a Promise.
From the documentation it is not immediately clear that key is the first value passed to the fetcher method.

const { data } = useSWR('/api/listings', fetcher)const fetcher = url => fetch(url).then(r => r.json()) // request to /api/listings

As long as the key remains the same, data is first returned from the cache and then a new request is made to revalidate it.

2. getStaticProps
Our real estate listing is essentially static. It can be changed, but not so frequently to require a status update for every single user’s request.

All pages, both the detail view ones and listing ones, have been built up using getStaticProps together with getStaticPaths. Next.js will statically pre-render all the paths specified by getStaticPaths at build time.

This function has been really useful for us to manage the internationalization (i.e. /case-in-vendita/milano for italian and /houses-for-sale/milan for english), as well as to generate the property details pages /houses-for-sale/milan/listing-id-123.

/// pages/[listing]/[city]/slug.tsxexport const getStaticPaths: GetStaticPaths = async () => {
const requestPaths = await fetch('/api/paths')
/**
requestPaths = {
listing: 'houses-for-sale',
city: 'milan',
slug: 'listing-id-123'
}
**/
return {
paths: [
{
params: requestPaths
}
],
fallback: true, // true or false
}
}

The params paths structure must resemble the directory structure under pages folder

pages/
[listing]/
index.tsx
[city]/
index.tsx
slug.tsx

The fallback parameter set to true is used to manage the new real estate listings. In this case, Next.js, instead of sending a 404 HTTP status code, try to run the getStaticProps function to generate a new page.

Usually, Nextjs will pre-render all pages at build time using the props returned by getStaticProps. However, we need to frequently update the listing pages. Next.js provided a solution for this matter, introducing the data revalidation every x seconds (with x = a fixed value set by devs).

At the first user’s request, getStaticProps rerun in order to render updated page.

export const getStaticProps: GetStaticProps = async (ctx) => {
const { data } = await request<Props>(endpoint, query, filters)
return {
props: {
listings: data,
},
revalidate: 60, // seconds
}
}

3. GraphQL
I have always used Apollo Client for my React applications, but I found a lot of confusion about how to best leverage it in an application that uses SSR.

After some research, also forced by the SWR library which only accepts a Promise returning function, I opted for graphql-request library.

Obviously, state and cache management parts are not provided, but that’s why I chose SWR first.

A small example:

import { request } from 'graphql-request'export const getStaticProps: GetStaticProps = async (ctx) => {
const endpoint = process.env.GRAPHQL_ENDPOINT
const queryCities = `
query Cities($filter: FilterByCountry!) {
cities(filter: $filter) {
description {
language
text
}
value
}
}
`
const filter = {
byCountry: 'ITALY',
}
const { cities } = await request<Props>(endpoint, queryCities, { filter }) return {
props: {
cities,
},
}
}

4. Emotion & Global Theme
Emotion is a library designed for writing css styles with JavaScript.

On the configuration side, it is just a matter of inserting a few lines in the .babelrc.js file to have it compiled using its preset

/// .babelrc.jsmodule.exports = {
presets: [
"next/babel",
[
"@emotion/babel-preset-css-prop",
{
sourceMap: true,
labelFormat: "[dirname]--[filename]--[local]",
},
],
],
plugins: [],
}

Using TypeScript you also have to globally define the references, I personally used next-env.d.ts file which is generated by Next.js.

/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="@emotion/core" />
/// <reference types="@emotion/styled" />

The easiest part is done!

To use the global theme feature you must first overwrite the native Next.js App component, which initializes all the pages, creating pages/_app.tsx file

/// pages/_app.tsximport Head from 'next/head'
import { AppProps } from 'next/app'
import { ThemeProvider } from 'emotion-theming'
import { theme } from '../config/theme.ts'
import '../reset.css'
import '../variables.css'
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
return (
<>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover, maximum-scale=1, user-scalable=0"
/>
</Head>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</>
)
}
export default MyApp

To use your configurations, you have to extend the default theme provided by Emotion library.

/// config/theme.tsimport styled, { CreateStyled } from '@emotion/styled'type Theme = {
colors: {
blue: string
}
}
export const theme: Theme = {
colors: {
blue: '#1f82c0',
},
}
export default styled as CreateStyled<Theme>

Using the default export, it’s possibile to create styled-component with your global theme props already built-in and TypeScript safe.

For example:

/// components/some-styles.tsimport styled from from '../config/theme.ts'export const Container = styled.div(({ theme }) => ({
color: theme.colors.blue,
width: 24,
...
}))

5. Env Variables
Inside the next.config.js file it is possible to define, in addition to a whole series of Next.js configurations, the accessibility of the environment variables.

/// next.config.jsmodule.exports = {
serverRuntimeConfig: {
// Will only be available on the server side: true,
graphqlEndpoint: process.env.GRAPHQL_ENDPOINT,
},
publicRuntimeConfig: {
// Will be available on both server and client
googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY,
},
}

This is really useful to recover your configurations on client side at runtime, when the Node.js process does not exist.

/// components/MyComponent/index.tsximport getConfig from 'next/config'export const MyComponent = () => {
const { publicRuntimeConfig } = getConfig()
const { googleMapsApiKey } = publicRuntimeConfig return (
<div>{googleMapsApiKey}</div>
)
}

6. SEO
Static page generation not only brings benefits to performance, but also SEO side. It is possible to generate all specific meta tags at build time and also more complex data structures such as JSON-LD. Structured data is a standardized format for providing information about a page and classifying the page content.

This is an example of a Header that we have generated for the single real estate ads:

/// headers/listing-detail.tsximport Head from 'next/head'
import getConfig from 'next/config'
export const HeaderListingDetail = ({ listing, language }) => {
const { publicRuntimeConfig } = getConfig()
const metaTitle = `create your own page title ${listing.title}`
const metaDescription = `create your own page description ${listing.description}`
return (
<Head>
<title>{metaTitle}</title>
<meta name="description" content={metaDescription} />
<meta property="og:locale" content={language} />
<meta property="og:type" content="article" />
<meta property="og:title" content={metaTitle} />
<meta property="og:description" content={metaDescription} />
<meta property="og:image" content={listing.photos[0].url} />
<meta
property="og:url"
content={`${publicRuntimeConfig.websiteMainUrl}/path/to/page/`}
/>
<meta property="og:site_name" content="Your Website" />
<link
rel="alternate"
hrefLang="en"
href={`${publicRuntimeConfig.websiteMainUrl}/path/to/english/page/`}
/>
<link
rel="alternate"
hrefLang="other language"
href={`${publicRuntimeConfig.websiteMainUrl}/path/to/other/language/page/`}
/>
<link
rel="canonical"
href={`${publicRuntimeConfig.websiteMainUrl}/path/to/page/`}
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:description" content={metaDescription} />
<meta name="twitter:title" content={metaTitle} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: `
{
"@type":[
"OfferForPurchase",
"${listing.residentialType}", // House, Apartment, SingleFamilyResidence
"RealEstateListing"
],
"@context":"http://schema.org",
"price": ${listing.sellingPrice},
"priceCurrency": "EUR",
"availability": "http://schema.org/InStock",
"name": "${listing.propertyName}",
"description": "${listing.propertyDescription}",
"image": "${listing.photos[0].url}",
"floorSize":{
"@type": "QuantitativeValue",
"@context": "http://schema.org",
"value": ${listing.commercialArea},
"unitCode": "MTK"
},
"numberOfRooms": ${listing.rooms},
"numberOfBathroomsTotal": ${listing.bathrooms},
"address":{
"@type": "PostalAddress",
"@context":"http://schema.org",
"streetAddress": "${listing.streetAddress}",
"addressLocality": "${listing.city}",
"addressRegion": "${listing.province}",
"postalCode": "${listing.zipCode}"
},
"geo":{
"@type": "GeoCoordinates",
"@context":"http://schema.org",
"latitude": ${listing.coordinates.latitude},
"longitude": ${listing.coordinates.longitude}
},
"url": "${publicRuntimeConfig.websiteMainUrl}/path/to/page/"
}
`,
}}
/>
</Head>
)
}

I hope I have given you some advice or ideas for your next application Next.js. In conclusion, I love Next.js and I encourage you to try it out!

--

--