Last active
May 13, 2021 12:16
-
-
Save harshmaur/d88ca14a97c1b5daee334dfe179e20fe to your computer and use it in GitHub Desktop.
Dynamic SEO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const VirtualRefinement = connectRefinementList(() => null) | |
const ConnectedSort = connectSortBy(({ refine, items, currentRefinement }) => ( | |
<div> | |
{map(items, item => ( | |
<SortingLink key={item.label} selected={currentRefinement === item.value} onClick={() => refine(item.value)}> | |
{item.label} | |
</SortingLink> | |
))} | |
</div> | |
)) | |
class ProductsApp extends PureComponent { | |
state = { | |
onlyInstock: true | |
} | |
onStockChange = e => { | |
this.setState({ onlyInstock: e.target.checked }) | |
} | |
render() { | |
const { | |
facets, | |
searchState, | |
onSearchStateChange, | |
resultsState, | |
brand_slug, | |
category_slug, | |
togglePopup, | |
...props | |
} = this.props | |
return ( | |
<IS | |
appId={ALGOLIA_APPID} | |
apiKey={ALGOLIA_APIKEY} | |
indexName={PRODUCTSINDEX} | |
searchState={searchState} | |
onSearchStateChange={onSearchStateChange} | |
resultsState={resultsState}> | |
<Configure {...props} /> | |
{this.state.onlyInstock && <VirtualRefinement attributeName="instock" defaultRefinement={[1]} />} | |
<ScrollTo> | |
<ListingWrapper> | |
<FilterContainer> | |
<div> | |
<FilterRefined> | |
<p> | |
Filter By{' '} | |
<span> | |
<ClearAll /> | |
</span> | |
</p> | |
<RefinedValue> | |
<CurrentRefinements | |
transformItems={items => | |
filter( | |
items, | |
item => | |
item.attributeName.indexOf('hierarchical') === -1 && | |
item.attributeName !== 'price' && | |
item.attributeName !== 'rating' && | |
item.attributeName !== 'instock' | |
) | |
} | |
/> | |
</RefinedValue> | |
</FilterRefined> | |
<CustomRefinementList title="Categories" expanded={true}> | |
<HierarchicalMenu | |
key="categories" | |
attributes={[ | |
'hierarchicalCategories.lvl0', | |
'hierarchicalCategories.lvl1', | |
'hierarchicalCategories.lvl2', | |
'hierarchicalCategories.lvl3', | |
'hierarchicalCategories.lvl4', | |
'hierarchicalCategories.lvl5', | |
'hierarchicalCategories.lvl6' | |
]} | |
showParentLevel={false} | |
/> | |
</CustomRefinementList> | |
<CustomRefinementList title="Price" expanded contentStyle={{ padding: '0px 15px 10px 16px' }}> | |
<RangeSlider attributeName="price" /> | |
</CustomRefinementList> | |
{map(facets, (item, i) => ( | |
<CustomRefinementList key={item} title={item.replace('properties.', '')} expanded={i < 3}> | |
<RefinementList | |
attributeName={item} | |
transformItems={data => orderBy(data, ['count', 'label'], ['desc', 'asc'])} | |
limitMin={50} | |
/> | |
</CustomRefinementList> | |
))} | |
<CustomRefinementList key="Rating" title="Rating" expanded> | |
<StarRating min={0} max={5} attributeName="rating" /> | |
</CustomRefinementList> | |
<CustomRefinementList key="instock" title="instock" expanded> | |
<label> | |
<input type="checkbox" checked={this.state.onlyInstock} onChange={this.onStockChange} /> | |
<span>Exclude Out of Stock</span> | |
</label> | |
</CustomRefinementList> | |
</div> | |
</FilterContainer> | |
<ListingContainer> | |
<TotleResult> | |
<ResultCountAndSearch> | |
<Stats | |
translations={{ | |
stats: n => | |
`${ | |
brand_slug ? startCase(brand_slug) + ' ' + startCase(category_slug) : startCase(category_slug) | |
} (${n} Results)` | |
}} | |
/> | |
<SearchInput> | |
<SearchBox placeholder="Search Results" togglePopup={togglePopup} /> | |
</SearchInput> | |
</ResultCountAndSearch> | |
</TotleResult> | |
<Sorting> | |
<div>Sort By:</div> | |
<ConnectedSort | |
items={[{ value: PRODUCTSINDEX, label: 'Most Relevant' }]} | |
defaultRefinement={PRODUCTSINDEX} | |
/> | |
</Sorting> | |
{/* Listing */} | |
<ProductTupleListing /> | |
{/* Listing */} | |
<Pagination | |
showPrevious={false} | |
showLast | |
showNext={false} | |
translations={{ first: 'FIRST PAGE', last: 'LAST PAGE' }} | |
/> | |
</ListingContainer> | |
</ListingWrapper> | |
</ScrollTo> | |
</IS> | |
) | |
} | |
} | |
const updateAfter = 700 | |
const searchStateToUrl = searchState => (searchState ? `${window.location.pathname}?${qs.stringify(searchState)}` : '') | |
class ProductListing extends PureComponent { | |
static async getInitialProps({ query, asPath, res, req }) { | |
const { category_slug = '', brand_slug = '' } = query | |
const q = query.query | |
// excluding instock 1 | |
const checkResponseFor404 = await index.search({ | |
query: q, | |
facets: '*', | |
filters: `${category_slug ? `category_slug: ${category_slug} ` : ''}${ | |
brand_slug ? `AND brand_slug: ${brand_slug}` : '' | |
}` | |
}) | |
let statusCode = 200 | |
if (checkResponseFor404.nbHits === 0) { | |
const data = await fetch(`${BASE_URL}/redirects?oldUrl=${req.originalUrl}`) | |
.then(r => r.json()) | |
.catch(() => []) | |
if (data.length) { | |
res.writeHead(301, { | |
Location: data[0].newUrl | |
}) | |
res.end() | |
res.finished = true | |
} else { | |
statusCode = 404 | |
res.statusCode = 404 | |
return { statusCode } | |
} | |
} | |
const [seoMeta, response, blogResults, dealsResults] = await Promise.all([ | |
fetch(`${BASE_URL}/comparisonSeo?categorySlug=${category_slug}&brandSlug=${brand_slug}`).then(r => r.json()), | |
index.search({ | |
query: q, | |
facets: '*', | |
filters: `instock: 1 OR instock: 0 ${category_slug ? `AND category_slug: ${category_slug}` : ''} ${ | |
brand_slug ? `AND brand_slug: ${brand_slug}` : '' | |
}` | |
}), | |
FR(BlogSlider, { query: `${brand_slug} ${category_slug} ${q}` }), | |
FR(DealTupleSlider, { query: `${category_slug} ${q}`, filters: 'isExpired: false' }) | |
]) | |
const facets = filter(Object.keys(response.facets), item => item.indexOf('properties') > -1) | |
// Remove brand facet if brand page | |
if (brand_slug) { | |
pull(facets, 'properties.brand') | |
} | |
const filters = `instock: 1 OR instock: 0 ${category_slug ? `AND category_slug: ${category_slug}` : ''} ${ | |
brand_slug ? `AND brand_slug: ${brand_slug}` : '' | |
}` | |
// send the url for seo | |
const sub = asPath.indexOf('?') > -1 ? asPath.indexOf('?') : asPath.length | |
const canonical = asPath.substring(0, sub) | |
const productsAppState = asPath.indexOf('?') > -1 ? qs.parse(asPath.substring(asPath.indexOf('?') + 1)) : {} | |
const productsAppResults = await FR(ProductsApp, { | |
filters, | |
facets, | |
brand_slug, | |
category_slug, | |
searchState: productsAppState | |
}) | |
return { | |
canonical, | |
facets, | |
seoMeta: seoMeta[0], | |
blogResults, | |
dealsResults, | |
productsAppResults, | |
productsAppState, | |
filters, | |
category_slug, | |
brand_slug | |
} | |
} | |
componentDidMount() { | |
this.setState({ searchState: qs.parse(window.location.search.slice(1)) }) | |
} | |
componentWillReceiveProps() { | |
this.setState({ searchState: qs.parse(window.location.search.slice(1)) }) | |
} | |
onSearchStateChange = searchState => { | |
clearTimeout(this.debouncedSetState) | |
const { category_slug = '', brand_slug = '' } = this.props.url.query | |
this.debouncedSetState = setTimeout(() => { | |
const href = searchStateToUrl(searchState) | |
// console.log(href) | |
Router.replace( | |
`/product-listing?category_slug=${category_slug}&brand_slug=${brand_slug}&${qs.stringify(searchState)}`, | |
href, | |
{ | |
shallow: true | |
} | |
) | |
}, updateAfter) | |
this.setState({ searchState }) | |
} | |
render() { | |
if (this.props.statusCode === 404) return <Error statusCode={404} /> | |
const { canonical, facets, filters, seoMeta = {}, category_slug, brand_slug } = this.props | |
let seoStr = '' | |
let hierarchy = [] | |
if (brand_slug) { | |
seoStr = `${startCase(brand_slug)} ${startCase(category_slug)}` | |
hierarchy = [ | |
{ name: 'Products', link: 'https://www.comparemunafa.com/search' }, | |
{ | |
name: startCase(category_slug), | |
link: `https://www.comparemunafa.com/c/${category_slug}` | |
}, | |
{ | |
name: startCase(brand_slug), | |
link: `https://www.comparemunafa.com/c/${category_slug}/${brand_slug}` | |
} | |
] | |
} else if (category_slug) { | |
seoStr = `${startCase(category_slug)}` | |
hierarchy = [ | |
{ name: 'Products', link: 'https://www.comparemunafa.com/search' }, | |
{ | |
name: startCase(category_slug), | |
link: `https://www.comparemunafa.com/c/${category_slug}` | |
} | |
] | |
} else { | |
seoStr = `Products` | |
hierarchy = [{ name: 'Products', link: 'https://www.comparemunafa.com/search' }] | |
} | |
const seoTitle = `Latest ${seoStr} Price List | Compare & Buy ${seoStr} Online ${format( | |
new Date(), | |
'YYYY Do MMMM' | |
)}` | |
const seoDesc = `Latest ${seoStr} list in India. Shop & compare ${ | |
seoStr | |
} at lowest price only on Comparemunafa and get best ${seoStr} offers, coupon & deals` | |
return ( | |
<Layout> | |
<Head title={seoTitle} description={seoDesc} url={canonical} /> | |
<Breadcrumb hierarchy={hierarchy} /> | |
<ProductsApp | |
filters={filters} | |
facets={facets} | |
brand_slug={brand_slug} | |
category_slug={category_slug} | |
togglePopup={this.props.togglePopup} | |
resultsState={this.props.productsAppResults} | |
onSearchStateChange={this.onSearchStateChange} | |
searchState={this.state && this.state.searchState ? this.state.searchState : this.props.productsAppState} | |
/> | |
{seoMeta.longDescription && ( | |
<Container backgroundColor="#fff" padding="0px 65px 20px"> | |
<SortDescription> | |
<h1>{seoTitle}</h1> | |
<div dangerouslySetInnerHTML={{ __html: seoMeta.longDescription }} /> | |
</SortDescription> | |
</Container> | |
)} | |
<DealTupleSlider | |
query={`${category_slug}`} | |
resultsState={this.props.dealsResults} | |
filters={'isExpired: false'} | |
/> | |
<BlogSlider query={`${brand_slug} ${category_slug}`} resultsState={this.props.blogResults} /> | |
</Layout> | |
) | |
} | |
} | |
export default withRedux(initStore, null, { togglePopup })(ProductListing) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment