블로그로 돌아가기

블로그의 RSS 피드 구현하기

Marco-2026-01-09 02:49:56

블로그를 운영하다 보면 RSS 피드는 필수 기능 중 하나입니다. Feedly, Inoreader 같은 RSS 리더를 통해 구독자들이 새 글을 쉽게 받아볼 수 있기 때문이죠. 이번 글에서는 Spring Boot와 Kotlin 환경에서 다국어(한국어/영어)를 지원하는 RSS 2.0 피드를 구현한 경험을 공유합니다.

들어가며

블로그를 운영하다 보면 RSS 피드는 필수 기능 중 하나입니다. Feedly, Inoreader 같은 RSS 리더를 통해 구독자들이 새 글을 쉽게 받아볼 수 있기 때문이죠. 이번 글에서는 Spring Boot와 Kotlin 환경에서 다국어(한국어/영어)를 지원하는 RSS 2.0 피드를 구현한 경험을 공유합니다.

기술 스택

  • Spring Boot 3.5.6
  • Kotlin 1.9.25
  • Rome 2.1.0 (RSS/Atom 피드 라이브러리)

Rome 라이브러리 선택 이유

RSS 피드를 구현하는 방법은 여러 가지가 있습니다:

방법장점단점
직접 XML 생성의존성 없음유지보수 어려움, 버그 가능성
Rome 라이브러리검증된 라이브러리, Spring 통합 용이추가 의존성
JAXBJava 표준설정 복잡

Rome은 RSS/Atom 피드 생성에 가장 널리 사용되는 Java 라이브러리이고, Spring의 AbstractRssFeedView와 자연스럽게 통합됩니다.

구현하기

  1. 의존성 추가
// build.gradle.kts
dependencies {
	implementation("com.rometools:rome:2.1.0")
}
  1. RSS 설정 Properties

환경별로 다른 base URL을 사용하기 위해 설정 클래스를 만들었습니다.

@ConfigurationProperties(prefix = "rss")
data class RssProperties(
	val baseUrl: String = "[http://localhost:3000](http://localhost:3000/)",
	val title: Map<String, String> = mapOf(
		"ko" to "기술 블로그",
		"en" to "Tech Blog",
	),
	val description: Map<String, String> = mapOf(
		"ko" to "기술 블로그 RSS 피드",
		"en" to "Tech Blog RSS Feed",
	),
	val maxItems: Int = 20,
)

application.yml

rss:
	base-url: ${RSS_BASE_URL:[http://localhost:3000](http://localhost:3000/)}
	title:
		ko: "Weggle Plus 기술 블로그"
		en: "Weggle Plus Tech Blog"
	max-items: 20
  1. RSS Feed View 구현

Spring의 AbstractRssFeedView를 상속받아 RSS 피드를 생성합니다.

@Component
@EnableConfigurationProperties(RssProperties::class)
class BlogRssFeedView(
private val rssProperties: RssProperties,
) : AbstractRssFeedView() {
  init {
      // 한글 인코딩을 위해 UTF-8 명시
      setContentType("application/rss+xml;charset=UTF-8")
  }

  override fun buildFeedMetadata(
      model: MutableMap<String, Any>,
      feed: Channel,
      request: HttpServletRequest,
  ) {
      val language = model["language"] as? String ?: "ko"

      feed.title = rssProperties.title[language] ?: rssProperties.title["ko"]
      feed.link = "${rssProperties.baseUrl}/blog"
      feed.description = rssProperties.description[language] ?: rssProperties.description["ko"]
      feed.language = language
  }

  override fun buildFeedItems(
      model: MutableMap<String, Any>,
      request: HttpServletRequest,
      response: HttpServletResponse,
  ): List<Item> {
      val posts = model["posts"] as? List<*> ?: emptyList<Any>()
      val language = model["language"] as? String ?: "ko"

      return posts.filterIsInstance<BlogPostResult>().map { post ->
          Item().apply {
              title = post.title
              link = buildPostLink(post.id, language)

              val guid = Guid()
              guid.value = link
              guid.isPermaLink = true
              this.guid = guid

              val desc = Description()
              desc.value = post.excerpt
              description = desc

              author = post.author
              categories = post.categories.map { category ->
                  RssCategory().apply {
                      value = category.value
                  }
              }
              pubDate = Date.from(post.createdAt.toInstant())
          }
      }
  }

  private fun buildPostLink(postId: Long, language: String): String =
      if (language == "en") {
          "${rssProperties.baseUrl}/blog/en/$postId"
      } else {
          "${rssProperties.baseUrl}/blog/$postId"
      }
}
  1. Controller 구현
@Controller
class BlogRssController(
private val blogRssService: BlogRssService,
private val blogRssFeedView: BlogRssFeedView,
) {
@GetMapping("/blog/posts/rss", produces = ["application/rss+xml"])
fun getKoRssFeed(model: Model): View {
val language = "ko"
val posts = blogRssService.getBlogPostsForRss(language)
      model.addAttribute("language", language)
      model.addAttribute("posts", posts)

      return blogRssFeedView
  }

  @GetMapping("/blog/en/posts/rss", produces = ["application/rss+xml"])
  fun getEnRssFeed(model: Model): View {
      val language = "en"
      val posts = blogRssService.getBlogPostsForRss(language)

      model.addAttribute("language", language)
      model.addAttribute("posts", posts)

      return blogRssFeedView
  }
}
  1. Security 설정

RSS 피드는 인증 없이 접근 가능해야 합니다.

.requestMatchers("/blog/posts/rss","/blog/en/posts/rss").permitAll()

트러블슈팅: 한글 인코딩 깨짐

처음 구현 후 테스트했을 때 한글이 깨지는 문제가 발생했습니다.

GA濡� 諛⑸Ц�� �곗씠�곕�...

원인은 Response의 Content-Type에 charset이 명시되지 않았기 때문입니다. AbstractRssFeedView의 기본 Content-Type은 application/rss+xml인데, charset이 없으면 브라우저나 RSS 리더가 임의의 인코딩으로 해석할 수 있습니다.

해결책으로 아래의 코드를 추가합니다.

init {
  setContentType("application/rss+xml;charset=UTF-8")
}

View 초기화 시점에 UTF-8을 명시적으로 설정하면 해결됩니다.

결과물

최종적으로 생성되는 RSS 피드 예시입니다:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
	<channel>
		<title>Weggle Plus 기술 블로그</title>
		<link>https://weggle-plus.co.kr/blog</link>
		<description>Weggle Plus 기술 블로그 RSS 피드</description>
		<language>ko</language>
		<item>
			<title>Spring Boot에서 RSS 피드 구현하기</title>
			<link>https://weggle-plus.co.kr/blog/1</link>
			<guid isPermaLink="true">https://weggle-plus.co.kr/blog/1</guid>
			<description>RSS 피드 구현 경험을 공유합니다...</description>
			<author>개발팀</author>
			<category>Spring Boot</category>
			<pubDate>Sat, 04 Jan 2026 10:00:00 +0900</pubDate>
		</item>
	</channel>
</rss>

프론트엔드에서 RSS URL 프록시하기

백엔드에서 RSS 피드 구현이 완료되었지만, 한 가지 고민이 있었습니다. RSS 피드 URL이 https://api.weggle-plus.co.kr/blog/posts/rss로 노출되는 것이 마음에 들지 않았습니다. 사용자에게 보여주고 싶은 URL은 https://weggle-plus.co.kr/blog/posts/rss.xml처럼 깔끔한 형태였습니다.

Vercel Rewrites 활용

프론트엔드가 Vercel에 배포되어 있다면 vercel.json의 rewrites 기능으로 간단히 해결할 수 있습니다.

{
	"rewrites": [
		{
			"source": "/blog/posts/rss.xml",
			"destination": "https://api.weggle-plus.co.kr/blog/posts/rss"
		},
		{
			"source": "/blog/en/posts/rss.xml",
			"destination": "https://api.weggle-plus.co.kr/blog/en/posts/rss"
		}
	]
}

Redirects vs Rewrites

여기서 중요한 점은 redirects가 아닌 rewrites를 사용해야 한다는 것입니다.

방식URL 변경API 도메인 노출RSS 리더 호환
redirectsOO△ (일부 리더에서 문제)
rewritesXXO

redirects는 브라우저에게 "다른 URL로 이동하라"고 알려주는 반면, rewrites는 서버 레벨에서 프록시처럼 동작합니다. 사용자와 RSS 리더 모두 weggle-plus.co.kr 도메인만 보게 됩니다.

주의사항

Vercel rewrites의 source는 반드시 /로 시작해야 합니다.

// ❌ 잘못된 예 "source": "blog/posts/rss.xml"

// ✅ 올바른 예 "source": "/blog/posts/rss.xml"

이 설정으로 사용자는 https://weggle-plus.co.kr/blog/posts/rss.xml로 접근하면서, 실제로는 백엔드 API의 RSS 피드를 받아볼 수 있습니다.

마치며

Spring Boot와 Rome 라이브러리를 활용하면 RSS 피드를 쉽게 구현할 수 있습니다. 특히 AbstractRssFeedView를 사용하면 View 패턴으로 깔끔하게 구현할 수 있고, 다국어 지원도 간단히 추가할 수 있습니다.

RSS 피드를 제공하면 사용자들이 Feedly 같은 RSS 리더로 블로그를 구독할 수 있어 접근성이 높아집니다. 기술 블로그를 운영하신다면 RSS 피드 제공을 고려해보세요!