{"id":28419,"date":"2022-04-06T02:43:34","date_gmt":"2022-04-06T02:43:34","guid":{"rendered":"http:\/\/next-track-image-impression"},"modified":"2022-04-06T02:43:34","modified_gmt":"2022-04-06T02:43:34","slug":"next-track-image-impression","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/","title":{"rendered":"Track Image impressions in Next.js"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>An impression in web statistics counts how many times a user views a particular element on a website. The element could be an ad, image, or something else.<\/p>\n<p>Impressions are an essential piece of data that analytics software tracks.<\/p>\n<p>In this article, we will learn how to use <code>react-intersection-observer<\/code> to track image impressions and then how to store them in our browser\u2019s localStorage.<\/p>\n<h2>Sandbox<\/h2>\n<p>The completed project is on <a href=\"https:\/\/codesandbox.io\/s\/image-intersection-observer-lkhpbg\">Codesandbox<\/a>. Fork it and run the code.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Codesandbox<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"image-intersection-observer-lkhpbg\"<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"image-intersection-observer\"<\/span> \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The source code is also available on <a href=\"https:\/\/github.com\/nefejames\/hackmamba-intersection-observer\">GitHub<\/a>.<\/p>\n<h2>Prerequisites<\/h2>\n<p>The knowledge of Next.js and the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\">localStorage API<\/a> is required to get the most out of this article. We will also use the <a href=\"https:\/\/github.com\/thebuilder\/react-intersection-observer\">react-intersection-observer<\/a> library to create the demo, so familiarity with it will be helpful.<\/p>\n<h2>Getting Started<\/h2>\n<p>We create a new Next.js application by running the command below in our terminal.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npx create-next-app next-image-intersection-observer-app\n<\/code><\/span><\/pre>\n<p>Next, we navigate into the project directory.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">cd next-image-intersection-observer-app\n<\/code><\/span><\/pre>\n<p>Then run the code below to install <code>react-intersection-observer<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install react-intersection-observer\n<\/code><\/span><\/pre>\n<h2>What we are building<\/h2>\n<p>We are building an image tracking application. As the user scrolls through an application, we track and increase an impression count when the user scrolls past a particular image.<\/p>\n<p>We will also add a visual indicator to the image to inform us when it is in view.<\/p>\n<p>This demo is similar to an analytics system that tracks impressions.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_CDB2BB2255AC8C37A4E607A450557CED586A36C04C8CF2AF785248125756D43C_1648011364074_ezgif.com-gif-maker14.gif\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"600\" height=\"338\"\/><\/p>\n<h2>Adding the Content<\/h2>\n<p>Update the <code>index.js<\/code> file with the code below:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/image\"<\/span>;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full h-full flex flex-col justify-center items-center px-10  pt-10 bg-zinc-900 relative\"<\/span>&gt;<\/span>\n    \n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl font-bold text-white mb-16 text-center\"<\/span>&gt;<\/span>\n        Next.js Intersection Observer Article\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      \n      {Array.from(Array(3).keys()).map((i) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-white text-2xl mb-16\"<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{i}<\/span>&gt;<\/span>\n          Irure pariatur velit est anim ipsum anim aliquip officia velit\n          consectetur. Duis sint ut consectetur ea anim. Sit proident culpa\n          velit officia do incididunt Lorem in deserunt non adipisicing occaecat\n          magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat\n          aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt\n          anim commodo amet reprehenderit.\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ))}\n      \n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-10\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"\/images\/1.png\"<\/span>\n          <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{300}<\/span>\n          <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{400}<\/span>\n          <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"something amazing\"<\/span>\n        \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      \n      {Array.from(Array(5).keys()).map((i) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-white text-2xl mb-16\"<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{i}<\/span>&gt;<\/span>\n          Irure pariatur velit est anim ipsum anim aliquip officia velit\n          consectetur. Duis sint ut consectetur ea anim. Sit proident culpa\n          velit officia do incididunt Lorem in deserunt non adipisicing occaecat\n          magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat\n          aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt\n          anim commodo amet reprehenderit.\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ))}\n      \n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here, we create an array, loop through some dummy text, and add the image between the text.<\/p>\n<h2>Integrating <code>react-intersection-observer<\/code><\/h2>\n<p>react-intersection-observer is an implementation of the Intersection Observer API for React. It informs us when an element enters or leaves the viewport.<\/p>\n<p>It provides a <code>useInView<\/code> hook we can use to monitor the <code>inView<\/code> state of components. There is also a custom <code>InView<\/code> component, which we will be using. We modify the index.js file to include these with:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/image\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { InView } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-intersection-observer\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full h-full flex flex-col justify-center items-center px-10  pt-10 bg-zinc-900 relative\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl font-bold text-white mb-16 text-center\"<\/span>&gt;<\/span>\n        Next.js Intersection Observer Article\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      \n      {Array.from(Array(3).keys()).map((i) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-white text-2xl mb-16\"<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{i}<\/span>&gt;<\/span>\n          Irure pariatur velit est anim ipsum anim aliquip officia velit\n          consectetur....\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ))}\n      \n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">InView<\/span> <span class=\"hljs-attr\">threshold<\/span>=<span class=\"hljs-string\">{0.6}<\/span>&gt;<\/span>\n        {({ ref, inView }) =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-10\"<\/span> <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">{ref}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n              <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"\/images\/1.png\"<\/span>\n              <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{300}<\/span>\n              <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{400}<\/span>\n              <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"something amazing\"<\/span>\n            \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">text-white<\/span> <span class=\"hljs-attr\">text-2xl<\/span> <span class=\"hljs-attr\">font-bold<\/span> ${\n                <span class=\"hljs-attr\">inView<\/span> ? \"<span class=\"hljs-attr\">text-green-600<\/span>\" <span class=\"hljs-attr\">:<\/span> \"<span class=\"hljs-attr\">text-red-600<\/span>\"\n              }`}\n            &gt;<\/span>{`Image in view? ${inView} `}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        )}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">InView<\/span>&gt;<\/span>\n      \n      {Array.from(Array(5).keys()).map((i) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-white text-2xl mb-16\"<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{i}<\/span>&gt;<\/span>\n          Irure pariatur velit est anim ipsum anim aliquip officia velit\n          consectetur....\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ))}\n      \n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the code above, we do the following:<\/p>\n<ul>\n<li>\n<p>Import <code>InView<\/code> from <code>react-intersection-observer<\/code>.<\/p>\n<\/li>\n<li>\n<p>Wrap our image with <code>InView<\/code> using the <a href=\"https:\/\/github.com\/thebuilder\/react-intersection-observer#render-props\">render props<\/a> method.<\/p>\n<\/li>\n<li>\n<p>We have access to an <code>inView<\/code> boolean in the render props, and we use it to add a visual indicator. The indicator informs us when the image is in view. We change the text color to green when the image is visible and red when it\u2019s not. We also change the content of the indicator\u2019s text based on the <code>inView<\/code> state.<\/p>\n<\/li>\n<\/ul>\n<h2>Setting up the Impression Counter<\/h2>\n<p>Having integrated <code>react-intersection-observer<\/code> and can track when the image is in view, let\u2019s set up the functionality for the impression counter. Updating the index.js file, we have:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/image\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { InView } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-intersection-observer\"<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ImpressionCounter<\/span>(<span class=\"hljs-params\">inView, count, setCount<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (inView) {\n    setCount(<span class=\"hljs-function\">(<span class=\"hljs-params\">count<\/span>) =&gt;<\/span> count + <span class=\"hljs-number\">1<\/span>);\n    localStorage.setItem(<span class=\"hljs-string\">\"impressionCount\"<\/span>, count);\n  }\n}\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;count, setCount] = useState(<span class=\"hljs-number\">0<\/span>);\n  \n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full h-full flex flex-col justify-center items-center px-10  pt-10 bg-zinc-900 relative\"<\/span>&gt;<\/span>\n    \n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl font-bold text-white mb-16 fixed top-10 right-10\"<\/span>&gt;<\/span>\n        Impression Count: {count}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n      \n      {Array.from(Array(3).keys()).map((i) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-white text-2xl mb-16\"<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{i}<\/span>&gt;<\/span>\n          Irure pariatur velit est anim ipsum anim aliquip officia velit\n          consectetur...\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ))}\n      \n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">InView<\/span>\n        <span class=\"hljs-attr\">threshold<\/span>=<span class=\"hljs-string\">{0.6}<\/span>\n        <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(inView)<\/span> =&gt;<\/span> ImpressionCounter(inView, count, setCount)}\n      &gt;\n        {({ ref, inView }) =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-10\"<\/span> <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">{ref}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n              <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"\/images\/1.png\"<\/span>\n              <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{300}<\/span>\n              <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{400}<\/span>\n              <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"something amazing\"<\/span>\n            \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">text-white<\/span> <span class=\"hljs-attr\">text-2xl<\/span> <span class=\"hljs-attr\">font-bold<\/span> ${\n                <span class=\"hljs-attr\">inView<\/span> ? \"<span class=\"hljs-attr\">text-green-600<\/span>\" <span class=\"hljs-attr\">:<\/span> \"<span class=\"hljs-attr\">text-red-600<\/span>\"\n              }`}\n            &gt;<\/span>{`Image in view? ${inView} `}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        )}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">InView<\/span>&gt;<\/span>\n      \n      {Array.from(Array(5).keys()).map((i) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-white text-2xl mb-16\"<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{i}<\/span>&gt;<\/span>\n          Irure pariatur velit est anim ipsum anim aliquip officia velit\n          consectetur...\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ))}\n      \n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the code above, we do the following:<\/p>\n<ul>\n<li>\n<p>Create a <code>count<\/code> state using the <code>useState<\/code> hook. <code>count<\/code> will hold the impression count of the image, which we want to store. We set its default value to 0.<\/p>\n<\/li>\n<li>\n<p>Create an <code>ImpressionCounter<\/code> function. It takes in <code>inView<\/code>, <code>count<\/code>, and <code>setCount<\/code> as arguments. We check if <code>inView<\/code> is true, and if so, increase the count by 1 and save that new count to localStorage.<\/p>\n<\/li>\n<li>\n<p>To call <code>ImpressionCounter<\/code>, we pass it to <code>InView<\/code>\u2019s <code>onChange<\/code> method. <code>onChange<\/code> will run and call <code>ImpressionCounter<\/code> only when the <code>inView<\/code> state changes.<\/p>\n<\/li>\n<li>\n<p>Display the total count in the <code>h2<\/code> tag so people can easily know how many impressions the image has.<\/p>\n<\/li>\n<\/ul>\n<p>Here\u2019s what the final page looks like. Notice the increasing impression count.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_CDB2BB2255AC8C37A4E607A450557CED586A36C04C8CF2AF785248125756D43C_1648011364074_ezgif.com-gif-maker14.gif\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"600\" height=\"338\"\/><\/p>\n<h2>Conclusion<\/h2>\n<p>In this article, we learned how to track image impressions using Next.js and <code>react-intersection-observer<\/code> and store those impressions in our browser\u2019s localStorage.<\/p>\n<h2>Further Reading<\/h2>\n<p><a href=\"https:\/\/github.com\/thebuilder\/react-intersection-observer\">React Intersection Observer<\/a> <a href=\"https:\/\/react-intersection-observer.vercel.app\/?path=\/docs\/inview-component--multiple-observers\">React Intersection Observer &#8211; Multiple Observers<\/a><\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28420,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,212,371],"class_list":["post-28419","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-next-js","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Track Image impressions in Next.js<\/title>\n<meta name=\"description\" content=\"How to use react-intersection-observer to track image impressions and then how to store them in our browser\u2019s localStorage.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Track Image impressions in Next.js\" \/>\n<meta property=\"og:description\" content=\"How to use react-intersection-observer to track image impressions and then how to store them in our browser\u2019s localStorage.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-04-06T02:43:34+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"1596\" \/>\n\t<meta property=\"og:image:height\" content=\"1024\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Track Image impressions in Next.js\",\"datePublished\":\"2022-04-06T02:43:34+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/\"},\"wordCount\":6,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"Next.js\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/\",\"name\":\"Track Image impressions in Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA\",\"datePublished\":\"2022-04-06T02:43:34+00:00\",\"description\":\"How to use react-intersection-observer to track image impressions and then how to store them in our browser\u2019s localStorage.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA\",\"width\":1596,\"height\":1024},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Track Image impressions in Next.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Track Image impressions in Next.js","description":"How to use react-intersection-observer to track image impressions and then how to store them in our browser\u2019s localStorage.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/","og_locale":"en_US","og_type":"article","og_title":"Track Image impressions in Next.js","og_description":"How to use react-intersection-observer to track image impressions and then how to store them in our browser\u2019s localStorage.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-04-06T02:43:34+00:00","og_image":[{"width":1596,"height":1024,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/"},"author":{"name":"","@id":""},"headline":"Track Image impressions in Next.js","datePublished":"2022-04-06T02:43:34+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/"},"wordCount":6,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA","keywords":["Guest Post","Image","Next.js","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/","name":"Track Image impressions in Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA","datePublished":"2022-04-06T02:43:34+00:00","description":"How to use react-intersection-observer to track image impressions and then how to store them in our browser\u2019s localStorage.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA","width":1596,"height":1024},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/next-track-image-impression\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Track Image impressions in Next.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924552\/Web_Assets\/blog\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0\/0933ea11fdebc8d9d06d6548a3c51c7562c8b11b-1596x1024-1_28420ea8a0.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28419","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28419"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28419\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28420"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28419"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28419"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28419"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}