<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anurag Ashok</title>
    <description>The latest articles on DEV Community by Anurag Ashok (@anuragashok).</description>
    <link>https://dev.to/anuragashok</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F542822%2Fdcc59873-79dc-494a-afa0-b64d366fdc9c.jpeg</url>
      <title>DEV Community: Anurag Ashok</title>
      <link>https://dev.to/anuragashok</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anuragashok"/>
    <language>en</language>
    <item>
      <title>Use Docker for local development</title>
      <dc:creator>Anurag Ashok</dc:creator>
      <pubDate>Sun, 03 Jan 2021 16:00:00 +0000</pubDate>
      <link>https://dev.to/anuragashok/use-docker-for-local-development-5gkp</link>
      <guid>https://dev.to/anuragashok/use-docker-for-local-development-5gkp</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ku5NWmkf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/lybfa03y94yw/31kEfTG8wIqkq8mixJ9skZ/6d86d7f4d0c700179d8eae5b31e01766/Untitled_design.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ku5NWmkf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/lybfa03y94yw/31kEfTG8wIqkq8mixJ9skZ/6d86d7f4d0c700179d8eae5b31e01766/Untitled_design.jpg" alt="a"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Photo by &lt;a href="https://www.pexels.com/@pixabay?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Pixabay&lt;/a&gt; from &lt;a href="https://www.pexels.com/photo/business-coffee-composition-computer-265667/?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Docker/Containers have become the de facto standard for building and deploying applications. The isolation, portability and easy scaling capabilities of containers make them the popular choice for app deployments.&lt;/p&gt;

&lt;p&gt;However, containers are not only for application deployments but can also be for local development. They can solve many developer issues. The use of Docker containers during development can have the following advantages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs on my machine = runs anywhere&lt;/li&gt;
&lt;li&gt;there are no cumbersome configuration/version incompatibilities&lt;/li&gt;
&lt;li&gt;The development environment is closer to production&lt;/li&gt;
&lt;li&gt;easy onboarding of new developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see how I use Docker for the development of this blog.&lt;/p&gt;
&lt;h2&gt;
  
  
  Base Image
&lt;/h2&gt;

&lt;p&gt;TheOverEngineeredBlog is built on next.js which needs node. Also, the package manager of choice is yarn. The official node image on docker hub is &lt;a href="https://hub.docker.com/_/node"&gt;here&lt;/a&gt;. It includes yarn too. This blog uses &lt;code&gt;node:lts&lt;/code&gt; image to get the latest lts version of node.&lt;/p&gt;
&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;

&lt;p&gt;I created a docker-compose.yml file at the root of the project to define the entire container configuration and add more containers if necessary later.&lt;/p&gt;
&lt;h6&gt;
  
  
  docker-compose.yml &lt;a href="https://github.com/anuragashok/theoverengineered.blog/blob/d784e3e072e19ae753cbe3fd39c64de86388e851/docker-compose.yml"&gt;View on GitHub&lt;/a&gt;
&lt;/h6&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.7'

services:
  runner: 
    image: node:lts
    ports: 
      - "$PORT:3000"
      - "$DEBUGPORT:9229"
    volumes:
    - .:/app:cached
    - yarn-cache-volume:/usr/local/share/.cache/yarn/v6:cached
    working_dir: /app
    command: "$COMMAND"

volumes:
  yarn-cache-volume:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The compose file defines a service named &lt;code&gt;runner&lt;/code&gt; using the base image "node:lts".&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ports&lt;/code&gt; section instructs Docker to expose ports 3000 and 9229 at $PORT and $DEBUGPORT on the host. PORT and DEBUGPORT are environment variables to configure the desired ports on the host.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;volumes&lt;/code&gt; section defines mounts and named volume. The root directory of the project is mounted to &lt;code&gt;/app&lt;/code&gt; inside the container. Also, it defines a named persistent volume for yarn cache. Docker manages this volume and persists it through the container stop/start. This cache reduces yarn execution time next time the container starts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;working_dir&lt;/code&gt; set the current directory to &lt;code&gt;./app&lt;/code&gt; to avoid changing the directory each time the container starts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;command&lt;/code&gt; is set to an environment variable $COMMAND. It can be supplied when invoking docker-compose.&lt;/p&gt;
&lt;h2&gt;
  
  
  RUN script
&lt;/h2&gt;

&lt;p&gt;I like to have a &lt;code&gt;run&lt;/code&gt; script to spawn the container using docker-compose to avoid writing the same commands each time.&lt;/p&gt;
&lt;h6&gt;
  
  
  run &lt;a href="https://github.com/anuragashok/theoverengineered.blog/blob/d784e3e072e19ae753cbe3fd39c64de86388e851/run"&gt;View on GitHub&lt;/a&gt;
&lt;/h6&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh
export PORT=${PORT:-3000}
export DEBUGPORT=${DEBUGPORT:-9229}
export COMMAND=${@:-"yarn dev"}
EXISTING_CONTAINER_ID=""
if [-n `docker-compose ps -q runner`]; then
    EXISTING_CONTAINER_ID=`docker-compose ps -q runner`;
elif [-n `docker ps -q --no-trunc | grep $(docker-compose ps -q runner)`]; then
    EXISTING_CONTAINER_ID=`docker ps -q --no-trunc | grep $(docker-compose ps -q runner)`;
fi

if [-z $EXISTING_CONTAINER_ID]; then
  COMMAND=${@:-"yarn dev"} docker-compose run --service-ports --rm runner
else
  echo "Existing container ${EXISTING_CONTAINER_ID}"
  docker exec -it ${EXISTING_CONTAINER_ID} ${COMMAND}
fi

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The script is executed like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PORT=&amp;lt;desired port on host&amp;gt; DEBUGPORT=&amp;lt;desired debug port on host&amp;gt;] ./run [&amp;lt;command&amp;gt;]

DEFAULTS PORT=3000 DEBUGPORT=9229 COMMAND="yarn dev"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sections in &lt;code&gt;[]&lt;/code&gt; are optional and have defaults set.&lt;/p&gt;

&lt;p&gt;To start the application, I need to write &lt;code&gt;./run&lt;/code&gt; on the shell. It starts the container, exposing the ports 3000 and 9229 on the host and then runs &lt;code&gt;yarn dev&lt;/code&gt; inside the container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CUCujSzr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://theoverengineered.blog/docker-local.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CUCujSzr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://theoverengineered.blog/docker-local.jpg" alt="Sample Output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any command can be executed inside the container by prefixing it with &lt;code&gt;./run&lt;/code&gt;E.g. To add a package, run &lt;code&gt;./run yarn add some-package-name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You could also do &lt;code&gt;./run bash&lt;/code&gt; to get a bash shell attached to the container. This bash shell can be used to execute commands inside the container without the prefix './run'&lt;/p&gt;

&lt;p&gt;The script also checks if a container is already running for the application and reuses the container to execute the command. Credits to &lt;a href="https://serverfault.com/a/935674/130937"&gt;this answer on ServerFault&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also write a similar script for windows machines using cmd/PowerShell.&lt;/p&gt;

&lt;p&gt;This setup has helped me enormously. I don't have to worry about installing different versions of node/java/python etc. Besides, now the only dependency for local development is, Docker!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>container</category>
      <category>development</category>
    </item>
    <item>
      <title>Generate RSS and Sitemap for Next.js JAMstack site</title>
      <dc:creator>Anurag Ashok</dc:creator>
      <pubDate>Sun, 27 Dec 2020 16:00:00 +0000</pubDate>
      <link>https://dev.to/anuragashok/generate-rss-and-sitemap-for-next-js-jamstack-site-5302</link>
      <guid>https://dev.to/anuragashok/generate-rss-and-sitemap-for-next-js-jamstack-site-5302</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HOvPvHrW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/lybfa03y94yw/3IejIjMkhHCU5xGOwXuW5o/6e1c1a898cbcde31682db277183fba0b/GENERATE_RSS_AND_SITEMAP.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HOvPvHrW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/lybfa03y94yw/3IejIjMkhHCU5xGOwXuW5o/6e1c1a898cbcde31682db277183fba0b/GENERATE_RSS_AND_SITEMAP.png" alt="a"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Image by &lt;a href="https://pixabay.com/users/mohamed_hassan-5229782/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=4059862"&gt;mohamed Hassan&lt;/a&gt; from &lt;a href="https://pixabay.com/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=4059862"&gt;Pixabay&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;RSS and sitemap are essential for blogs today. RSS Feeds let users subscribe to your content and improves user engagement. On the other hand, a sitemap is for search engines to find and index your content.&lt;/p&gt;

&lt;p&gt;When using a CMS like wordpress etc.., the RSS and sitemap XML files are generated at runtime. However, for JAMStack websites, we would like to create them at the build stage. I was not able to find an OOTB solution for next.js to create these files. The XML files for RSS and sitemap are not too complex to generate. Hence, I decided not to introduce any third-party dependencies to generate these files.&lt;/p&gt;

&lt;p&gt;There are mainly three questions to answer when generating these files. Where, When and How?&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where to place
&lt;/h2&gt;

&lt;p&gt;The convention followed by many is to place rss.xml and sitemap.xml at the root of the website. Sitemaps can be split into files and referenced from the main sitemap.xml. This is needed when sitemaps grow very huge. We will stick to single sitemap.xml for now.&lt;/p&gt;

&lt;p&gt;Next.js routing does not support files that are not content. So, what we can do is, to place these files in the &lt;code&gt;public&lt;/code&gt; directory. Next's static file serving feature serves the files under this directory at the root of the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. When to generate
&lt;/h2&gt;

&lt;p&gt;We have to generate the files inside the public directory during the build. During the build, the &lt;code&gt;getStaticProps&lt;/code&gt; function gets invoked for each page. We can leverage this function to create our XML files.&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;getStaticProps&lt;/code&gt; function of any page component to create the files. However, this will add unnecessary code to the pages. So, I created a dummy.tsx page. The &lt;code&gt;getStaticProps&lt;/code&gt; of this page component will contain the additional build time processing logic.&lt;/p&gt;

&lt;p&gt;If anyone visits /dummy we should probably return 404 and ignore the page from any search engine indexing.&lt;/p&gt;

&lt;h6&gt;
  
  
  dummy.tsx &lt;a href="https://github.com/anuragashok/theoverengineered.blog/blob/1b1f462a7cb697ecdcd222313d913101de176dfa/src/pages/dummy.tsx#L15-L22"&gt;View on GitHub&lt;/a&gt;
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Dummy: React.FC = () =&amp;gt; (
  &amp;lt;&amp;gt;
    &amp;lt;Head&amp;gt;
      &amp;lt;meta name="robots" content="noindex" /&amp;gt;
    &amp;lt;/Head&amp;gt;
    &amp;lt;DefaultErrorPage statusCode={404} /&amp;gt;
  &amp;lt;/&amp;gt;
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. How to generate
&lt;/h2&gt;

&lt;p&gt;The creation of XML files is a matter of iterating over the content and generating the XML tags. This can be implemented in the &lt;code&gt;getStaticProps&lt;/code&gt; function of &lt;code&gt;pages/dummy.tsx&lt;/code&gt;. You can find the snippets of the code below. You can refer to GitHub repo for this blog for the full code sample.&lt;/p&gt;

&lt;h6&gt;
  
  
  dummy.tsx - getStaticProps() &lt;a href="https://github.com/anuragashok/theoverengineered.blog/blob/1b1f462a7cb697ecdcd222313d913101de176dfa/src/pages/dummy.tsx#L24-L34"&gt;View on GitHub&lt;/a&gt;
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getStaticProps: GetStaticProps = async () =&amp;gt; {
  const posts = await getPosts();
  generateRss(posts);

  const pages = await getAllContent();
  generateSitemap(pages);

  return {
    props: {},
  };
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  generateRss() &lt;a href="https://github.com/anuragashok/theoverengineered.blog/blob/1b1f462a7cb697ecdcd222313d913101de176dfa/src/lib/rss.ts"&gt;View on GitHub&lt;/a&gt;
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const generateRssItem = (post: Post): string =&amp;gt; `
  &amp;lt;item&amp;gt;
    &amp;lt;guid&amp;gt;${getFullUrl(`blog/${post.slug}`)}&amp;lt;/guid&amp;gt;
    &amp;lt;title&amp;gt;${post.title}&amp;lt;/title&amp;gt;
    &amp;lt;link&amp;gt;${getFullUrl(`blog/${post.slug}`)}&amp;lt;/link&amp;gt;
    &amp;lt;description&amp;gt;${post.description}&amp;lt;/description&amp;gt;
    &amp;lt;pubDate&amp;gt;${new Date(post.publishDate).toUTCString()}&amp;lt;/pubDate&amp;gt;
  &amp;lt;/item&amp;gt;
`;

export default (posts: Post[]): void =&amp;gt; {
  const rss = `&amp;lt;rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"&amp;gt;
    &amp;lt;channel&amp;gt;
      &amp;lt;title&amp;gt;${SITE_TITLE}&amp;lt;/title&amp;gt;
      &amp;lt;link&amp;gt;${getFullUrl('')}&amp;lt;/link&amp;gt;
      &amp;lt;description&amp;gt;${SITE_TITLE}&amp;lt;/description&amp;gt;
      &amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;
      &amp;lt;lastBuildDate&amp;gt;${new Date(posts[0].publishDate).toUTCString()}&amp;lt;/lastBuildDate&amp;gt;
      &amp;lt;atom:link href="${getFullUrl('rss.xml')}" rel="self" type="application/rss+xml"/&amp;gt;
      ${posts.map(generateRssItem).join('')}
    &amp;lt;/channel&amp;gt;
  &amp;lt;/rss&amp;gt;`;
  fs.writeFileSync('./public/rss.xml', rss);
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  generateSitemap() &lt;a href="https://github.com/anuragashok/theoverengineered.blog/blob/1b1f462a7cb697ecdcd222313d913101de176dfa/src/lib/sitemap.ts"&gt;View on GitHub&lt;/a&gt;
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default (pages: Content[]): void =&amp;gt; {
  const links = compose(map(mapToSitemapEntry))(pages);

  if (fs.existsSync(SITEMAP_PATH)) {
    fs.unlinkSync(SITEMAP_PATH);
  }
  const stream = fs.createWriteStream(SITEMAP_PATH, { flags: 'a' });
  stream.write(`&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
  &amp;lt;urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&amp;gt;`);
  links.forEach((item) =&amp;gt; {
    stream.write(`
      &amp;lt;url&amp;gt;
        &amp;lt;loc&amp;gt;${item.url}&amp;lt;/loc&amp;gt;
        &amp;lt;changefreq&amp;gt;${item.changefreq}&amp;lt;/changefreq&amp;gt;
        &amp;lt;priority&amp;gt;${item.priority}&amp;lt;/priority&amp;gt;
      &amp;lt;/url&amp;gt;`);
  });
  stream.write('\n');
  stream.write('&amp;lt;/urlset&amp;gt;');
  stream.end();
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can later validate these xml files against the specs at &lt;a href="https://validator.w3.org/feed/"&gt;W3C Feed Validator&lt;/a&gt; and &lt;a href="https://www.xml-sitemaps.com/validate-xml-sitemap.html"&gt;XML Sitemap Validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rsssitemapbloggingja</category>
    </item>
  </channel>
</rss>
