안드로이드 앱 구글 Admob Native in-feed 광고 넣기
안드로이드 구글 Admob Native in-feed 광고 넣기
- 본 앱 경우 하단 슬롯에 광고를 넣고자 함.
- 슬롯을 클릭하면 카드 페이지 모달 발생(실제 광고)
- 현재 PWA로 만들어진 상태 (ai studio)

1. 애드몹 가입 계정생성
- 계정을 만들고, 지불방식(payment 설정 까지 마치면 활성화됨)
Google AdMob: 모바일 앱 수익 창출
인앱 광고를 사용하여 모바일 앱에서 더 많은 수익을 창출하고, 사용이 간편한 도구를 통해 유용한 분석 정보를 얻고 앱을 성장시켜 보세요.
admob.google.com
2. 애드몹 광고단위 생성
- 이번 경우 인 피드 방식 광고 설정을 위해
- native in-feed 방식을 생성함

3. 애드몹 설치
npm install @capacitor-community/admob
4. nativeAdService.ts 파일 생성
// nativeAdService.ts
import { AdMob, NativeAdOptions } from '@capacitor-community/admob';
import { Capacitor } from '@capacitor/core';
const TEST_NATIVE_AD_ID = 'ca-app-pub-3940256099942544/2247696110';
const PROD_NATIVE_AD_ID = 'ca-app-pub-3933370356899243/6341217418';
const IS_DEV = import.meta.env.DEV;
const NATIVE_AD_ID = IS_DEV ? TEST_NATIVE_AD_ID : PROD_NATIVE_AD_ID;
export interface NativeAdData {
id: string;
isAd: true;
headline: string;
body: string;
callToAction: string;
advertiser: string;
icon?: string;
images?: string[];
price?: string;
store?: string;
starRating?: number;
}
// [수정] adCache 변수 자체를 사용하지 않거나 항상 빈 배열로 초기화
let isLoading = false;
export const loadNativeAds = async (count: number = 3): Promise<NativeAdData[]> => {
if (!Capacitor.isNativePlatform()) {
return []; // 웹 환경에선 무조건 빈 배열 (목업 차단)
}
try {
await AdMob.initialize();
} catch (e) {
console.log("AdMob initialization check:", e);
}
if (isLoading) return []; // 로딩 중이면 이전 캐시 대신 빈 배열 반환
isLoading = true;
try {
const options: NativeAdOptions = {
adId: NATIVE_AD_ID,
};
// AdMob 실제 광고 로드
const result = await AdMob.prepareNativeAd(options);
const nativeAd: NativeAdData = {
id: `native-ad-${Date.now()}`,
isAd: true,
headline: result.headline || '',
body: result.body || '',
callToAction: result.callToAction || 'Learn More',
advertiser: result.advertiser || 'Sponsored',
icon: result.icon,
images: result.images || [],
price: result.price,
store: result.store,
starRating: result.starRating,
};
return [nativeAd]; // 로드 성공 시 실제 데이터만 반환
} catch (error) {
console.error('Failed to load native ads:', error);
return []; // [핵심] 실패 시 절대 getMockAds를 호출하지 않고 빈 배열 반환
} finally {
isLoading = false;
}
};
// [삭제] 기존에 존재하던 getMockAds 함수는 아예 지우거나 아래처럼 비워두세요.
const getMockAds = (count: number): NativeAdData[] => [];
5. PWA 앱 내에서 만든 광고 슬롯 > 안드로이드 적용 가능 확인
- 커서ai 에서 확인함 = 즉, 리액트 컴포넌트로 되어있어 안드로이드에서도 유지된다는 의미
네이티브 광고 데이터 로딩: AdMob.prepareNativeAd()로 네이티브 광고 데이터를 가져옴
React 컴포넌트로 렌더링: 가져온 데이터를 AdSlot 컴포넌트로 표시
인피드 방식: 일정 목록 사이에 광고가 자연스럽게 삽입됨
6. 애드몹 기기설정 (테스트를 위한 설정 필요)
- 이미 구글 앱스토어에 등록되어있다면 바로 실제 ID-key를 사용해도 될테지만,
- 난 아직 미등록 상태이며, 내 기기에서 테스트가 필요함.

7. 기기 추가 내용

기기 ID 찾는 방법
- 설정 / google 서비스 / 광고 / 하단
"이 기기의 광고 ID' 아래 코드를 위의 입력란에 넣음.


8. 애드몹 ID 입력을 통해 테스트 광고 연결 확인
- 먼저 브라우저에서 확인 (브라우저에서는 빌드한 버전이 아니므로 테스트용 ID가 적용됨)
- 하단에 ad 뱃지가 보이고, 카드 페이지 접근 버튼이 생겼다. 성공!!

아래 작업을 통해서 최종 구현된 화면임.
component/ AdSlot.tsx 파일 수정!
import React from 'react';
import { Advertisement, Language } from '../types';
interface Props {ad: Advertisement;
lang: Language;
onClick: (ad: Advertisement) => void;
getRemainingTimeStr: (date: string) => React.ReactNode;
}
const AdSlot: React.FC<Props> = ({ ad, onClick }) => {
return (
<div
id={`native-ad-container-${ad.id}`}
onClick={() => onClick(ad)}
className="native-ad-placeholder flex-shrink-0 w-44 sm:w-60 p-5 sm:p-6 rounded-[1.5rem] sm:rounded-[2.5rem] border border-white/20 bg-white/5 relative overflow-hidden flex flex-col justify-between transition-all hover:border-white/40 cursor-pointer"
>
{/* 상단: 배지 및 광고주 정보 */}
<div className="flex justify-between items-start mb-3">
<span className="text-[8px] sm:text-[9px] font-black px-2 py-0.5 rounded-full bg-white/10 text-white/50">
AD
</span>
<span className="text-[8px] sm:text-[9px] font-medium text-white/20 truncate max-w-[100px]">
{ad.advertiser || 'Sponsored'}
</span>
</div>
{/* 중단: 광고 본문 (이미지 + 텍스트) */}
<div className="flex flex-col flex-grow overflow-hidden">
{ad.images && ad.images.length > 0 && (
<div className="w-full h-20 sm:h-24 mb-2 rounded-xl overflow-hidden bg-white/5">
<img
src={ad.images[0]}
alt="Ad Media"
className="w-full h-full object-cover opacity-80"
/>
</div>
)}
<p className="text-sm sm:text-base font-black text-white truncate mb-1">
{ad.headline}
</p>
<p className="text-[10px] sm:text-[11px] text-white/40 line-clamp-2 leading-tight">
{ad.body}
</p>
</div>
{/* 하단: 행동 유도 버튼 (CTA) */}
<div className="mt-3">
<div className="w-full py-2 rounded-xl bg-lime-400/20 border border-lime-400/30 text-center">
<span className="text-[11px] sm:text-xs font-black text-lime-400">
{ad.callToAction || 'Learn More'}
</span>
</div>
</div>
</div>
);
};
export default AdSlot;
필요에 따라 nativeAdService.ts 파일 수정
- 주요한 것은 test ID 와 prod ID 를 분리 선언하므로 테스트 확인 및 관리에 용이함.
- 이건 PWA 상태이고, 안드로이드로 넘어가면 AndroidManifest.xml 파일로 넘어가면서 prod ID 만 남게됨.
- 즉 무조건 실제 ID로 접속하게 되므로, 테스트 기기 등록이 반드시 필요함. 그냥 테스트하다가 광고 누르면 계정에 문제 발생 가능!
// nativeAdService.ts
import { AdMob, NativeAdOptions } from '@capacitor-community/admob';
import { Capacitor } from '@capacitor/core';
const TEST_NATIVE_AD_ID = 'ca-app-pub-3940256099942544/2247696110'; // 테스트용 ID
const PROD_NATIVE_AD_ID = 'ca-app-pub-3933370356899243/xxxxxxxxx'; // 내 실제 ID
const IS_DEV = import.meta.env.DEV;
const NATIVE_AD_ID = IS_DEV ? TEST_NATIVE_AD_ID : PROD_NATIVE_AD_ID;
export interface NativeAdData {
id: string;
isAd: true;
headline: string;
body: string;
callToAction: string;
advertiser: string;
icon?: string;
images?: string[];
price?: string;
store?: string;
starRating?: number;
}
// [수정] adCache 변수 자체를 사용하지 않거나 항상 빈 배열로 초기화
let isLoading = false;
export const loadNativeAds = async (count: number = 3): Promise<NativeAdData[]> => {
if (!Capacitor.isNativePlatform()) {
return []; // 웹 환경에선 무조건 빈 배열 (목업 차단)
}
try {
await AdMob.initialize();
} catch (e) {
console.log("AdMob initialization check:", e);
}
if (isLoading) return []; // 로딩 중이면 이전 캐시 대신 빈 배열 반환
isLoading = true;
try {
const options: NativeAdOptions = {
adId: NATIVE_AD_ID, // 자동으로 테스트와 프로덕트를 구분하여 적용
};
// AdMob 실제 광고 로드
const result = await AdMob.prepareNativeAd(options);
const nativeAd: NativeAdData = {
id: `native-ad-${Date.now()}`,
isAd: true,
headline: result.headline || '',
body: result.body || '',
callToAction: result.callToAction || 'Learn More',
advertiser: result.advertiser || 'Sponsored',
icon: result.icon,
images: result.images || [],
price: result.price,
store: result.store,
starRating: result.starRating,
};
return [nativeAd]; // 로드 성공 시 실제 데이터만 반환
} catch (error) {
console.error('Failed to load native ads:', error);
return []; // [핵심] 실패 시 절대 getMockAds를 호출하지 않고 빈 배열 반환
} finally {
isLoading = false;
}
};
// [삭제] 기존에 존재하던 getMockAds 함수는 아예 지우거나 아래처럼 비워두세요.
const getMockAds = (count: number): NativeAdData[] => [];
9. 기기 연결 안드로이드 확인
- 기기 연결 테스트는 빌드 후에 적용되므로, 테스트ID 적용을 하지 못하게 되어있음. (위에 설명됨)
- 자동으로 프로덕트 ID가 들어가게 되어있음.
- 하지만 애드몹에 기기 등록을 해둔 상태이므로, 테스트 광고가 뜨게 되는 원리
- 카드 연결 버튼을 누르면 아래와 같이 미리 세팅해둔 디자인 UI에 맞춰 테스트 광고 레이어 모달을 확인할 수 있음.

!! 중간에 뻘짓하며 배운 내용 요약
1. 안드로이드 빌드 후 런하면서 버전 문제로 개고생. > 36버전을 낮춰 35로 통일
compileSdkVersion = 35
targetSdkVersion = 35
2. 그밖에 환경변수 설정 포함 처음 안드로이드 스튜디오 실행으로 생기는 흔한 문제들
3. flatDir 구식 방법 찾아서 제거하느라 개고생 > fileTree 방식으로 통일,
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'])
settings.ts 에도 남아있어서 뒤늦게 발견 삭제!!
4. 애드몹 네이티브 광고 삽입 문제 개고생 > 안드로이드에서 해결이 안되서, ai studio에서 프롬프트로 해결
" 광고에는 목업광고를 넣지 않는다. GPT(웹광고)방식 사용안함. 안드로이드앱으로 빌드할 것임. 안드로이드 스튜디오에서 설정 가능하도록 세팅만 해주길 요청"
(주: 네이티브 인피드 광고 최소 사이즈는 최소 32x32dp 만 넘으면 되므로 자유로운 사이즈, 위치에 넣을 수 있어 UX에 좋음)
이렇게 새로 다운로드한 PWA 파일을 빌드한 후 안드로이드 스튜디오 ai 에서 다시 한 번 확인하면서 정리.
AdSlot.ts 경우 빈 템플릿만 생성되었으므로 안드로이드에서 알려주는 코드로 변경하여 적용하니 원활히 수행됨.